Improving Developer Experience with Multi-Targeted Visual Studio Projects

In my previous post on using multi-targeted cross platform projects I showed how you can add additional target frameworks to allow the addition of platform specific code to a library. One of the downsides of this approach (versus perhaps using a shared project) is that the library gets built once for every target framework that’s … Continue reading “Improving Developer Experience with Multi-Targeted Visual Studio Projects”

In my previous post on using multi-targeted cross platform projects I showed how you can add additional target frameworks to allow the addition of platform specific code to a library. One of the downsides of this approach (versus perhaps using a shared project) is that the library gets built once for every target framework that’s specified. In this post I’m going to show you how you can use the combination of a simple script and Solution Filters to optimise both the time it takes to load your solution in Visual Studio and the time it takes to compile your application.

Firstly, we’re going to adjust the Core project so that we can adjust which target frameworks are include based on a solution property called TargetsToBuild. If this is set to All, we’ll simply use the TargetFrameworks we defined in the previous post (i.e. .NET Standard, iOS, Android and Windows (UAP) when building on Windows). However, if TargetsToBuild is not equal to All, we’ll set the TargetFrameworks to be just the specific target framework that we’re interested in.

<Project Sdk="MSBuild.Sdk.Extras">
	<PropertyGroup Condition=" '$(TargetsToBuild)' == 'All' ">
		<TargetFrameworks>netstandard2.0;xamarinios10;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>
	</PropertyGroup>

Note: I quite often will include netstandard2.0 in all of the TargetFrameworks to make sure the .NET Standard 2.0 build doesn’t break as we’re changing the Core library. This is a personal choice and you’ll definitely get better performance out of not having it build every time.

Ok, so the question is, where does TargetsToBuild get set. For this, we’re going to include a file called Directory.build.props in the solution folder. You can think of this as a file where you can define properties and include items and references across the entire solution. In this case, we’re going to set the contents to the following.

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

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

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

There’s two parts to this:

  • Firstly, if the Configuration is not set to Debug, the TargetsToBuild is always set to All. This is never modified, and is a fail safe to ensure all target frameworks are built when doing a Release build.
  • Secondly, the TargetsToBuild is set to the value held by TargetsToBuildDeveloperOverride when the Configuration is Debug. This variable is currently set to All.

The property TargetsToBuildDeveloperOverride essentially has four possible values: All, Android, iOS and Windows. Changing this value will adjust the target frameworks for the Core project. I would recommend restarting Visual Studio as the support for dynamically switching target frameworks in any given session isn’t there yet and doesn’t work 100% reliably.

The next piece to the puzzle is to use Solution Filters to limit which projects are loaded. By limiting which projects are loaded, your solution will load quicker and will also build faster (depending on what your build configuration settings are). To create a Solution Filter, simply unload the projects you don’t want loaded, then right-click on the solution node in Solution Explorer and click Save as Solution Filter. I’ve created the following solution filters:

  • MultiTargetSample.all.slnf: All projects are loaded
  • MultiTargetSample.Android.slnf: Core and Android head project loaded
  • MultiTargetSample.iOS.slnf: Core and iOS head project loaded
  • MultiTargetSample.Windows.slnf: Core and Windows head project loaded

The solution filter file, SLNF, is just a JSON file that lists which projects should be loaded. For example the MultiTargetSample.Windows.slnf file is as follows:

{
  "solution": {
    "path": "MultiTargetSample.sln",
    "projects": [
      "MultiTarget.Core\\MultiTarget.Core.csproj",
      "MultiTarget.Windows\\MultiTarget.Windows.csproj"
    ]
  }
}

The last piece of the puzzle is to combine the solution filters with the TargetsToBuildDeveloperOverride property. For example, if we want to be working on the Android application, we want to set the TargetsToBuildDeveloperOverride property to Android and then load the MultiTargetSample.Android.slnf solution filter. This is easily done using a couple of simple batch script.

Firstly, a generic script (LaunchVS.bat) that takes a parameter (the platform) and adjusts the TargetsToBuildDeveloperOverride property and then launches the appropriate solution filter.

powershell -Command "(gc Directory.build.props) -replace '[a-zA-Z]*', '%~1 ' | Out-File -encoding ASCII Directory.build.props"
start MultiTargetSample.%~1.slnf

Then, we have a script for each platform (eg LaunchVS.Windows.bat) which simply has the following command

LaunchVS.bat Windows

When you double-click on LaunchVS.Windows.bat in File Explorer, Visual Studio is launched with only the Core and Windows projects loaded. The Core library will only build the UAP target framework. This significantly improves both the solution load time and the build time, making working with MultiTargeted projects much easier.

Important: You might think that this isn’t important when you’re first starting out but I would highly recommend adding this to your solution from the get-go. It will save you hours of time both now and when your solution grows. Visual Studio does not handle large solutions with projects that have many target frameworks. Applying these changes will ensure Visual Studio continues to operate properly

Cross-Platform Libraries with Multi-Targeting for Xamarin, Uno and Windows Development

The story of cross platform development for Microsoft developers has been one marked by iteration after iteration of .NET framework fragmentation. With .NET 5 the promise is that we’ll return to having a single framework that will be used everywhere…. Sorry, what?? when did we ever have a single framework? For as long as I … Continue reading “Cross-Platform Libraries with Multi-Targeting for Xamarin, Uno and Windows Development”

The story of cross platform development for Microsoft developers has been one marked by iteration after iteration of .NET framework fragmentation. With .NET 5 the promise is that we’ll return to having a single framework that will be used everywhere…. Sorry, what?? when did we ever have a single framework? For as long as I can recall, developers working on mobile and/or IoT have always had a different framework to target (.NET CF, .NET MF, WinPhone (whatever that .NET version was called), UWP etc). However, one thing that hasn’t changed is the fact that in order to target different platforms, you inevitably have to write code that’s specific to that platform. Even during the announcement of MAUI, the new single project structure has specific files/code etc for iOS, Android, Windows etc. This problem isn’t going away, so let’s discuss how we deal with this issue today.

The scenario I’m going to cover in the post is relatively simple. I have three “head” or “target platform” projects (Windows, Android, iOS) that are going to reference a common library (Core). Whilst most of the Core library is common code that is compatible with .NET Standard 2.0, there’s some code that is specific to each platform. Rather than create a separate platform specific library, we can include this specific logic by setting up the Core library to be multi-targeted – meaning that a different DLL is generated for each supported platform. Luckily Visual Studio knows how to handle this, so you’re not left working out how to reference each DLL from the head projects.

Project Setup

Let’s get started. First up, we’ll create a blank solution. If you start with one of the head projects your solution will end up being called something list XXX.Windows.

Search for Solution and select Blank Solution
Name your solution

Next up, let’s create the three head projects: Windows (UWP), Android and iOS

Search for UWP and select the Blank App (Universal Windows) template
Name you Windows head project – I like to use the Windows suffix but uwp is also often used
Search for droid and select the Android App (Xamarin) template
Name your Android head project
Search for iOS and select the iOS App (Xamarin) template
Name your iOS head project

At this point, if you’re working on a Mac, you could also add a MacOS project template. For some reason this template isn’t available in Visual Studio for Windows (because no developer would ever want to create the MacOS project when working on windows …. I guess the same (lack of) logic is used on VS for Mac where you can’t create a UWP project).

Next up, we’ll create the common library that each of the head projects will reference.

Search for .net standard and select the Class Library (.NET Standard) template
Name the .NET Standard library

Common Code

So that we can see how this library will work, we’ll simply expose a string constant that can be referenced and displayed in each head project. We’ll rename the Class1.cs file to AppProperties.cs and allow Visual Studio to rename the corresponding class. We’ll then add a string constant as follows:

namespace MultiTarget.Core
{
    public class AppProperties
    {
        public const string AppName = "MultiTarget Sample";
    }
}

In each of the head projects we’ll add an text/label element to the UI and set the value to be AppProperties.AppName. Rather than having to use the Add Reference workflow (i.e. right click on the head project and click Add Reference), we’ll simply type the AppProperties class name and use the prompt in the code editor to Add reference to the MultiTarget.Core project.

Type Ctrl+. to get the code editor suggestions to appear.

Now we can run each of the head projects and see the same output on the screen which should say “MultiTarget Sample” (I don’t have an iOS device/simulator handy, so I don’t have a screenshot of it running on iOS).

Android – MultiTarget Sample
Windows – MultiTarget Sample

Platform Specific Code

The next step is to add platform specific code. In this case we’re going to augment the existing AppProperties class by adding a platform specific partial class. In Solution Explorer we can copy the AppProperties.cs and paste it into the same project. The copied file can then be renamed based on the platform. For example (see image below) the Windows specific code would be in a file called AppProperties.windows.cs.

Note: The actual name of the platform specific files doesn’t have any intrinsic meaning. As we’ll see in a minute, we will use the platform specific suffix to define which files will be included for each platform.

Inside the platform specific file we’ll add another constant called PlatformName. For example, the AppProperties.windows.cs includes a PlatformName constant that returns “Windows.”

using System;

namespace MultiTarget.Core
{
    public partial class AppProperties
    {
        public const string PlatformName = "Windows";
    }
}

We’re also going to update the AppName constant in the AppProperties.cs file to include the PlatformName. We also needed to add the partial keyword to the class definition.

namespace MultiTarget.Core
{
    public partial class AppProperties
    {
        public const string AppName = "MultiTarget Sample - " + PlatformName;
    }
}

After adding files for each platform, if you attempt to build the Core project it will fail with an error indicating that there are multiple definitions of the PlatformName constant. What we need to do is to only include one platform specific file at a time.

MultiTarget Project

Currently the Core project is configured with a single TargetFramework, netstandard2.0. This means that the only API that can be accessed are those defined by netstandard2.0. If we want to include platform specific code, or want to access platform specific APIs, we need to change the project to target multiple frameworks. Here’s the current project XML for the Core library

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>
</Project>

In order to target multiple frameworks, we can just change the TargetFramework element to TargetFrameworks, and add multiple frameworks, semicolon delimited (eg <TargetFrameworks>netstandard2.0;net451</TargetFrameworks> ). However, out of the box, this doesn’t support the various target framework monikers that are used across different platforms. To overcome this limitation, we need to change the Sdk attribute from “Microsoft.NET.Sdk” to “MSBuild.Sdk.Extras”. We also need to create a file called global.json in the solution folder with the following version information for the MSBuild.Sdk.Extra nuget package:

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

Note: Alternatively you can specify the version number in the Sdk attribute (eg Sdk=”MSBuild.Sdk.Extras/2.0.54″ ).

With these changes made, we can update the project file to include multiple target frameworks:

<Project Sdk="MSBuild.Sdk.Extras">
	<PropertyGroup>
		<TargetFrameworks>netstandard2.0;xamarinios10;monoandroid10.0;</TargetFrameworks>
		<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">uap10.0.16299;$(TargetFrameworks)</TargetFrameworks>
	</PropertyGroup>
</Project>

Note: we’ve included a conditional TargetFrameworks element so that the Windows (i.e. UWP) target platform is only include if the host OS is Windows. This is important because the UWP platform can’t be built using Visual Studio for Mac, so making this target framework conditional on the OS ensures the project can be loaded and built using Visual Studio for Mac.

At this point it’s worth noting that by including additional target frameworks, you’re effectively increasing the number of steps in the build. For example, when we build the Core library, we see that four DLLs are built – effectively increasing the built time by four times!!!

The new project format includes all files nested in the project folder by default. This means that we need to change the default behaviour for the platform specific files so that they’re excluded by default. Then we can specifically include files for each platform that is built. The project file now looks like this:

<Project Sdk="MSBuild.Sdk.Extras">
	<PropertyGroup>
		<TargetFrameworks>netstandard2.0;xamarinios10;monoandroid10.0;</TargetFrameworks>
		<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">uap10.0.16299;$(TargetFrameworks)</TargetFrameworks>
	</PropertyGroup>

	<ItemGroup>
		<Compile Remove="**\*.netstandard.cs" />
		<Compile Remove="**\*.droid.cs" />
		<Compile Remove="**\*.ios.cs" />
		<Compile Remove="**\*.windows.cs" />
	</ItemGroup>

	<ItemGroup Condition=" $(TargetFramework.StartsWith('netstandard')) ">
		<Compile Include="**\*.netstandard.cs" />
	</ItemGroup>

	<ItemGroup Condition=" $(TargetFramework.StartsWith('xamarinios')) ">
		<Compile Include="**\*.ios.cs" />
	</ItemGroup>

	<ItemGroup Condition=" $(TargetFramework.StartsWith('monoandroid')) ">
		<Compile Include="**\*.droid.cs" />
	</ItemGroup>

	<ItemGroup Condition=" $(TargetFramework.StartsWith('uap')) ">
		<Compile Include="**\*.windows.cs" />
	</ItemGroup>
</Project>

And that’s it – building the Core library will result in four DLLs, each one having a different value for PlatformName, and thus AppName. We didn’t need to make any changes to the head projects.

This technique for including platform specific code in a common library is used by popular frameworks such as ReactiveUI and MvvmCross, as well as Microsoft libraries such as Xamarin Essentials.

Source code

Microsoft Build 2020: Uno Platform + WinUI = Future of Cross Platform Apps

I’ve been a long time supporter of the work that the team at Uno Platform have been doing. The announcement today of v3.0 of the Uno Platform comes hot on the heals of releases 2.3 and 2.4 that introduced AndroidX and MacOS support. This isn’t just any old announcement, v3 aligns the Uno Platform with … Continue reading “Microsoft Build 2020: Uno Platform + WinUI = Future of Cross Platform Apps”

I’ve been a long time supporter of the work that the team at Uno Platform have been doing. The announcement today of v3.0 of the Uno Platform comes hot on the heals of releases 2.3 and 2.4 that introduced AndroidX and MacOS support. This isn’t just any old announcement, v3 aligns the Uno Platform with the preview of WinUI 3.

At Built to Roam we’ve been building XAML based applications across a variety of technologies: WPF, Silverlight, WinPhone/Win8, UWP. Each iteration of Windows 10, new features were added to UWP but this limited the reach of the application to devices running the latest version of Windows 10. With WinUI 3.0 Microsoft is detaching the XAML platform away from the Windows version – this means that WinUI can iterate faster and bring newer controls and features to developers. In addition, WinUI also brings with it support for Win32 based applications – more on WinUI here.

So, why is it important that Uno Platform supports WinUI 3.0? Well, if you think of WinUI extending UWP to support all Windows 10 devices, then the addition of the Uno Platform takes WinUI across iOS, Android, macOS and Web via WebAssembly #WinUIEverywhere.

For more information, check out the Announcement of Uno Platform 3.0

Look iOS Developer, No Mac Required – Build an iOS Application using Xamarin and Visual Studio for Windows without using a Mac

If you’re a die-hard Windows user, like me, you’ll be excited to know that you can now build iOS application using Xamarin (and Xamarin.Forms) and Visual Studio, without having to buy or use a Mac. That’s right for development, you no longer need to invest, or carry around, a Mac. In this post I’ll walk you through how to enable this feature.

If you’re a die-hard Windows user, like me, you’ll be excited to know that you can now build iOS application using Xamarin (and Xamarin.Forms) and Visual Studio, without having to buy or use a Mac. That’s right for development, you no longer need to invest, or carry around, a Mac. In this post I’ll walk you through how to enable this feature.

I’m working in the latest preview of Visual Studio 2019 and as of now, to use Xamarin Hot Restart (the feature that powers the no Mac development experience), you need to check the Enable Xamarin Hot Restart option under Preview Features in the Tools, Options dialog.

After enabling Xamarin Hot Restart, make sure you restart Visual Studio. Next, set your iOS project to be the startup project by right-clicking the iOS project in Solution Explorer, followed by the Set as Startup Project menu item.

Make sure in the toolbar the option next to the play button says Local Device. Click the play button, which will kick off the Setup Hot Restart experience.

There are a couple of steps to jump through the first time you want to use Hot Restart. Make sure you follow the instructions to avoid having to redo steps.

The first step in the process it to Download iTunes – make sure you click the Download iTunes button. Do NOT install iTunes from the Microsoft Store. If you’ve done this previously, make sure you uninstall it, and then install it by clicking the Download iTunes button.

Clicking the Download iTunes button will open your default browser but it will attempt to immediately download the file – make sure you check out the downloads so you can launch the file once it’s downloaded.

Step through the iTunes installer.

Once iTunes is installed the Setup Hot Restart process will detect the presence of iTunes (note that it will not detect iTunes if you’re installed it from the Microsoft Store).

Next, make sure you have an iOS device plugged in and that you’ve clicked the Trust option on the device when prompted to trust the connected computer. The Setup Hot Restart process should detect the attached device.

Next, sign into your Apple Developer account.

And select the Development Team you want to use for provisioning.

After completing the Setup Hot Restart process you should see that the build process will continue and that in the Output window you’ll see the iPA being created and subsequently pushed to the device.

When prompted, you’ll need to launch the installed application on the iOS device – this manual step is required in order for Visual Studio to attach the debugger.

And there you have it – you now have an iOS application being debugged using Visual Studio on an actual device with no Mac required!!!

Pipeline Templates: Complete Azure Pipelines Example for a Uno Project for iOS, Android and Windows

My last post was a bit of a long one as it covered a bunch of steps for setting up the bits and pieces required for signing an application for different platforms. In this post I just wanted to provide a complete example that shows a single multi-stage (6 in total) Azure Pipelines pipeline for … Continue reading “Pipeline Templates: Complete Azure Pipelines Example for a Uno Project for iOS, Android and Windows”

My last post was a bit of a long one as it covered a bunch of steps for setting up the bits and pieces required for signing an application for different platforms. In this post I just wanted to provide a complete example that shows a single multi-stage (6 in total) Azure Pipelines pipeline for building a Uno application for iOS, Android and Windows (UWP) and releasing them to App Center.

Secure Files

In my last post I showed how to create and populate Secure Files in Azure Pipelines. Any certificate or provisioning profile you need to use in your pipeline should be added to the Secure Files section of the Library in Azure Pipeline. My list of Secure Files looks like this:

Here we can see that we have the signing certificates for iOS and Windows, and the keystore for Android. Then we have two iOS provisioning profiles, one for my XF application and the other for my Uno application.

Variable Groups

I’ve extracted most of the variables I use in my pipeline into one of two variable groups:

The Common Build Variables are those variables that can be reused across multiple projects.

The Inspector Uno Build Variables are those variables that are specific to this project. For example it includes the AppCenter ids for the iOS, Android and Windows applications. It also includes the iOS provisioning profile which is specifically tied to this application.

Pipeline

Here’s the entire pipeline:

resources:
  repositories:
    - repository: builttoroam_templates
      type: github
      name: builttoroam/pipeline_templates
      ref: refs/tags/v0.5.0
      endpoint: github_connection
  
variables:
  - group: 'Common Build Variables'
  - group: 'Inspector Uno Build Variables'
  - name: ios_enabled
    value: 'true'
  - name: windows_enabled
    value: 'true'
  - name: android_enabled
    value: 'true'
 
stages:
- template:  azure/mobile/[email protected]_templates
  parameters:
    # Stage name and whether it's enabled
    stage_name: 'Build_Android'
    build_android_enabled: $(android_enabled)
    # Version information
    full_version_number: '$(version_prefix).$(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: $(solution_file)
    solution_build_configuration: $(solution_build_config)
    # Output information
    artifact_folder: $(artifact_android_folder)
    application_package: $(android_application_package)

- template:  azure/mobile/[email protected]_templates
  parameters:
    # Stage name and dependencies
    stage_name: 'Deploy_Android'
    depends_on: 'Build_Android'
    deploy_appcenter_enabled: $(android_enabled)
    environment_name: $(appcenter_environment)
    # Build artifacts
    artifact_folder: $(artifact_android_folder)
    application_package: $(android_application_package)
    # 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)


- template:  azure/mobile/[email protected]_templates
  parameters:
    # Stage name and whether it's enabled
    stage_name: 'Build_Windows'
    build_windows_enabled: $(windows_enabled)
    # Version information
    full_version_number: '$(version_prefix).$(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: $(solution_file)
    solution_build_configuration: $(solution_build_config)
    # Output information
    artifact_folder: $(artifact_windows_folder)
    application_package: $(windows_application_package)

- template:  azure/mobile/[email protected]_templates
  parameters:
    # Stage name and dependencies
    stage_name: 'Deploy_Windows'
    depends_on: 'Build_Windows'
    deploy_appcenter_enabled: $(windows_enabled)
    environment_name: $(appcenter_environment)
    # Build artifacts
    artifact_folder: $(artifact_windows_folder)
    application_package: $(windows_application_package)
    # Deployment to AppCenter
    appcenter_service_connection: $(appcenter_service_connection)
    appcenter_organisation: $(appcenter_organisation)
    appcenter_applicationid: $(appcenter_windows_appid)

- template:  azure/mobile/[email protected]_templates
  parameters:
    # Stage name and whether it's enabled
    stage_name: 'Build_iOS' 
    build_ios_enabled: $(ios_enabled)
    # Version information
    full_version_number: '$(version_prefix).$(Build.BuildId)'
    # Solution to build
    solution_filename: $(solution_file)
    solution_build_configuration: $(solution_build_config)
    # Signing information
    ios_plist_filename: 'src/Apps/DotNet/Uno/InspectorUno/InspectorUno/InspectorUno.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: $(artifact_ios_folder)
    application_package: $(ios_application_package)

- template:  azure/mobile/[email protected]_templates
  parameters:
    # Stage name and dependencies
    stage_name: 'Deploy_iOS'
    depends_on: 'Build_iOS'
    deploy_appcenter_enabled: $(ios_enabled)
    environment_name: $(appcenter_environment)
    # Build artifacts
    artifact_folder: $(artifact_ios_folder)
    application_package: $(ios_application_package)
    # Deployment to AppCenter
    appcenter_service_connection: $(appcenter_service_connection)
    appcenter_organisation: $(appcenter_organisation)
    appcenter_applicationid: $(appcenter_ios_appid)


Pipeline Templates v0.5.0

Most of the v0.5.0 release has been tidying things up, reducing the number of required parameters by making the template smarter and increasing consistency across the templates

Breaking Changes:

  • build-xamarin-android.yml – changed build_platform to solution_target_platform parameter
  • build-xamarin-windows.yml – changed build_platform to solution_target_platform parameter
  • build-xamarin-windows.yml – changed windows_appxupload_name parameter to windows_upload_name to reflect support for msix

Other Changes:

  • build-xamarin-[iOS/android/windows].yml – added depends_on parameter so that the stages can be ordered
  • build-xamarin-[iOS/android/windows].yml – artifact_name, artifact_folder and application_package are no longer required and have default values
  • build-xamarin-android.yml – supports building either aab or apk based on the application_package parameter
  • build-xamarin-windows.yml – windows_upload_name parameter isn’t required as it has a default value based on the bundle name
  • deploy-appcenter.yml – added conditions to all steps so that pipeline doesn’t break if build stage doesn’t generate any output
  • All template – documentation added to parameters and steps

Pipeline Templates: Building and Deploying Uno Apps for iOS, Android and Windows

In my previous posts covering the Pipeline Templates I’ve discussed building a Xamarin.Forms apps for iOS, Android and Windows (UWP) and subsequently deploying them to AppCenter. In this post we’re going to look at doing the same with a Uno application. Given that Uno is built on top of the core Xamarin functionality, the process … Continue reading “Pipeline Templates: Building and Deploying Uno Apps for iOS, Android and Windows”

In my previous posts covering the Pipeline Templates I’ve discussed building a Xamarin.Forms apps for iOS, Android and Windows (UWP) and subsequently deploying them to AppCenter. In this post we’re going to look at doing the same with a Uno application. Given that Uno is built on top of the core Xamarin functionality, the process for both build and deploy should be fairly similar. Rather than just stepping through using the template, I’m going to cover creating a new Uno project and setting up the corresponding multi-stage pipeline to build and deploy to AppCenter.

Also, I’ve pushed v0.3.0 of the pipeline templates, which I’ll summarise at the end of this post.

Creating a Uno Application

Let’s get into it and start by creating a Uno application. Before proceeding, make sure you grab the latest Uno extension for Visual Studio that includes the project templates. From the Create a new project dialog, enter “uno” into the search box and then select the Cross-Platform App (Uno Platform) template.

Next, enter a name and location for your new application

When you hit the Create button, Visual Studio will generate a new solution with five projects, representing the head projects for iOS, Android, Windows (UWP) and Web (WASM), along with a shared project.

After creating the application, you should go through each platform and check that it builds and runs. The WASM project will take a while to build and run the first time as it needs to download some components first – don’t be alarmed it nothing appears to happen for a long time.

Build Configuration

One thing I do like to tidy up for my applications is the build configurations. This is often overlooked by developers and then wonder why they have to wait around for say the Android project to build, when they have the UWP project selected as their start up project. Here are the Release configurations I have set for the different platforms

Release Configuration with Any CPU
Release Configuration with iPhone
Release Configuration with x64 (x86 and ARM platforms are the same except the Platform for InspectorUno.UWP is x86 and ARM respectively)

Building Uno Applications

We’ll go through each Uno application separately and I’ll endeavor to highlight typical pain points that developers face. If you’re using the pipeline_templates and you run into issues, message me on Twitter and I’ll help you out. Where there are common frustrations, I’ll work to improve the templates to make them easier to use.

Let’s start by creating a new pipeline in Azure DevOps. I’m not going to step through the pipeline wizard but for what we want to do, just select the appropriate source code respository and an empty YAML file. When you get to the YAML editor, enter the following.

trigger: none
resources:
  repositories:
    - repository: builttoroam_templates
      type: github
      name: builttoroam/pipeline_templates
      ref: refs/tags/v0.3.0
      endpoint: github_connection
  
variables:
  - group: 'Uno Build Variables'

There are three parts to this inital YAML. Firstly, we’re disabling the CI trigger whilst we’re configuring the build. As we’ll be committing changes to both the build pipeline and source code, it’s as well to only have builds triggered when you’re ready, otherwise you’ll be continually cancelling builds.

Next, the resources section is where we pull in the pipeline templates. In this case we’re targeting the v0.3.0 release to ensure the templates don’t change and break our builds in the future.

Lastly, we’re pulling in the Uno Build Variables variable group. You can pick whatever name you want for this group but it needs to match the name of the variable group defined under the Library tab on Azure DevOps.

Android – Build

Starting with Android we’re going to add stages to the YAML file (note you only need to include the stages element once and then add the individual stages by referencing the corresponding template). Here’s the Android build

stages:
- template:  azure/mobile/[email protected]_templates
  parameters:
    stage_name: 'Build_Android'
    build_android: $(android_enabled)
    solution_filename: 'src/Apps/DotNet/Inspector.Uno.sln'
    solution_build_configuration: 'Release'
    build_number: '$(Build.BuildId)'
    full_version_number: '$(version_prefix).$(Build.BuildId)'
    artifact_name: 'inspector-build'
    artifact_folder: 'Android_output'
    application_package: 'Inspector-Uno-Android.aab'
    secure_file_keystore_filename: '$(android_keystore_filename)'
    keystore_alias: '$(android_keystore_alias)'
    keystore_password: '$(android_keystore_password)'

There are a few things you need to have setup for this build to work:

  • solution_filename – make sure the path to your solution file is correct, relevant to the root of your code repository.
  • $(version_prefix) – make sure the version_prefix variable exists in your variable group and has the format X.Y (eg 1.0)
  • $(android_keystore_filename) – make sure the android_keystore_filename variable exists in your variable group and that it’s value matches the Secure file name of the keystore file uploaded to the Secure files under the Library in Azure DevOps.
  • $(android_keystore_alias) – make sure the android_keystore_alias variable exists in your variable group and that it is the the alias that you specified when creating the keystore. This variable should be marked as private by clicking the lock beside it in the portal – this hides it both in the variable group editor in the portal and in the log files.
  • $(android_keystore_password) – make sure the android_keystore_password variable exists in your variable group and that it is the password you specified when creating the keystore. Again this should be marked as private.

Frustratingly, the Android build didn’t just work for a couple of reasons:

  • Firstly, there was an issue where one of the images has a file name that isn’t supported by the version of Visual Studio and/or Android tooling on the windows-latest build agent, resulting in the following error:
    2020-02-08T06:04:55.7036355Z ##[error]src\Apps\DotNet\Uno\InspectorUno\InspectorUno\InspectorUno.Shared\Assets\Square44x44Logo.targetsize-24_altform-unplated.png(0,0): Error APT0003: Invalid file name: It must contain only [^a-zA-Z0-9_.]+.
    I simply excluded the file Square44x44Logo.targetsize-24_altform-unplated.png from the shared project
  • Secondly, the build agent include the Android NDK but doesn’t specify the appropriate environment variables (eg ANDROID_NDK_HOME), resulting in the following error:
    2020-02-07T12:56:19.5428557Z ##[error]C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Xamarin\Android\Xamarin.Android.Common.targets(2843,3): Error XA5101: Could not locate the Android NDK. Please make sure the Android NDK is installed in the Android SDK Manager, or if using a custom NDK path, please ensure the $(AndroidNdkDirectory) MSBuild property is set to the custom path.
    The solution here was for me to update the build-xamarin-android.yml to include these environment variables. You shouldn’t run into this error if you’re using v0.3.0 of the pipeline templates.

Android – Deploy

Here’s the YAML for deploying the Android application to AppCenter

- template:  azure/mobile/[email protected]_templates
  parameters:
    stage_name: 'Deploy_Android'
    depends_on: 'Build_Android'
    environment_name: 'Inspector-Alpha'
    artifact_name: 'inspector-build'
    artifact_folder: 'Android_output'
    application_package: 'Inspector-Uno-Android.aab'
    appcenter_service_connection: 'AppCenterInspectorCI'
    appcenter_organisation: 'thenickrandolph'
    appcenter_applicationid: 'Inspector-Uno-Alpha'
    appcenter_release_notes: 'Release from deploy pipeline'
    secure_file_keystore_filename: '$(android_keystore_filename)'
    keystore_alias: '$(android_keystore_alias)'
    keystore_password: '$(android_keystore_password)'

Again there are a few things you need to check here but most importantly for this to work you need to setup an application in AppCenter. I created a new application

After creating the application, look at the url that appears in the browser. The piece highlighted in green is the value you need to set for the appcenter_organisation and the yellow is for the appcenter_applicationid.

You also need to make sure you setup a Service Connection between Azure DevOps and AppCenter (in this case it was given the name ‘AppCenterInspectorCI’ and assigned to the appcenter_service_connection property)

The other properties you need to check are:

  • depends_on – make sure this matches the stage_name property set on the android build stage
  • artifact_name, artifact_folder and application_package – make sure these match the corresponding value on the android build stage
  • secure_file_keystore_filename, keystore_alias and keystore_password – these should be set to the same as on the android build stage and are required so that the release process can extract and sign a fat apk from the aab file.

iOS – Build

Before we get into building your iOS application, let’s make sure you have both a signing certificate and a provisioning profile ready to go. If you have both of these, you can jump over the next two sections. These instructions can be stepped through on Windows, without the need for a Mac!

certificate

  • Install OpenSSL (Download from https://slproweb.com/products/Win32OpenSSL.html)
  • Open command prompt
  • Add OpenSSL to path (alternatively you can permanently add it to your Path environment variable if you want to have it accessible across all command prompts)
    set PATH=%PATH%;C:\Program Files\OpenSSL-Win64\bin\
  • Generate the signing request
    openssl genrsa -out ios_distribution.key 2048
    openssl req -new -key ios_distribution.key -out CertificateSigningRequest.certSigningRequest
  • Sign into the Apple Developer portal and go to Certificates (https://developer.apple.com/account/resources/certificates)
  • Click the + button to start the process of adding a new certificate
  • Select Apple Distribution from the list of certificate types and then Continue
  • Use the Choose File link to select the CertificateSigningRequest.certSigningRequest file you created in the previous steps, and then Continue
  • Important – Make sure you click the Download button to download your file. It’ll download as a file called distribution.cer unless you choose to rename it.
  • Move the distribution.cer file to the same folder that you’ve been working in
  • Complete the certificate creation from the command prompt (making sure you change PASSWORD to be a password of your choice)
    openssl x509 -in distribution.cer -inform DER -out ios_distribution.pem -outform PEM
    openssl pkcs12 -export -inkey ios_distribution.key -in ios_distribution.pem -out ios_distribution.p12 -passout pass:PASSWORD

With these steps completed you have a certificate file, ios_distribution.p12, which you can upload as a Secure File to Azure DevOps.

You’ll also need the signing identity, which we can get directly from the ios_distribution.pem that was generated along the way.
openssl x509 -text -noout -in ios_distribution.pem

Application Identifier

Before you can create a provisioning profile you need to setup an identity for your application in the Apple Developer portal. Go to the Identities tab and click on the + button to register a new application.

Select App IDs and then Continue

Enter both a Description and a Bundle ID. You may also need to select an App ID Prefix if you belong to a team. Next click Continue, followed by the Register button to complete the process of registering your application’s identity.

Note: Currently you will need to update the Info.plist file of your iOS application to set the CFBundleIdentifier to match the Bundle ID specified when creating the App ID.

Provisioning Profile

To create the provisioning profile, click on the Profiles tab in the Apple Developer portal. Click the + button to start the process of creating a new provisioning profile.

Select the Ad Hoc option under Distribution and then Continue.

Select the App ID that you previously created, then Continue

Select the certificate that needs to be used to sign the application, then Continue

Select the devices that will be able to install the application, then Continue

Give the provisioning profile a name and then click Generate.

Download the provisioning profile and add it as a Secure File in the Library in Azure DevOps. You’ll also need the provisioning profile id, which you can extract from the provisioning profile using any text editor. Open the provisioning profile and look for the UUID key; the string value immediately after it is the profile id.

Ok, we’re now ready for the build stage

- template:  azure/mobile/[email protected]_templates
  parameters:
    stage_name: 'Build_iOS' 
    build_ios: true
    solution_filename: 'src/Apps/DotNet/Inspector.Uno.sln'
    solution_build_configuration: 'Release'
    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)'
    build_number: '$(Build.BuildId)'
    full_version_number: '$(version_prefix).$(Build.BuildId)'
    ios_signing_identity: '$(ios_signing_identity)'
    ios_provisioning_profile_id: '$(ios_provisioning_profile_id)'
    artifact_name: 'inspector-build'
    artifact_folder: 'iOS_output'
    application_package: 'Inspector-Uno-iOS.ipa'

iOS – Deploy

The deploy stage is relatively simple

- template:  azure/mobile/[email protected]_templates
  parameters:
    stage_name: 'Deploy_iOS'
    depends_on: 'Build_iOS'
    environment_name: 'Inspector-Alpha'
    artifact_name: 'inspector-build'
    artifact_folder: 'iOS_output'
    application_package: 'Inspector-Uno-iOS.ipa'
    appcenter_service_connection: 'AppCenterInspectorCI'
    appcenter_organisation: 'thenickrandolph'
    appcenter_applicationid: 'Inspector-Uno-iOS-Alpha'
    appcenter_release_notes: 'Release from deploy pipeline'

Windows – Build

The build stage for Windows (UWP) is

- template:  azure/mobile/[email protected]_templates
  parameters:
    stage_name: 'Build_Windows'
    build_windows: true
    solution_filename: 'src/Apps/DotNet/Inspector.Uno.sln'
    solution_build_configuration: 'Release'
    uwpBuildPlatform: 'x86'
    build_number: '$(Build.BuildId)'
    full_version_number: '$(version_prefix).$(Build.BuildId)'
    windows_cert_securefiles_filename: '$(windows_signing_certificate_securefiles_filename)'
    windows_cert_password: '$(windows_signing_certificate_password)'
    artifact_name: 'inspector-build'
    artifact_folder: 'Windows_output'
    application_package: 'Inspector-Uno-Windows.msixbundle'
    windows_appxupload_name: 'Inspector-Uno-Windows.msixupload'

In order to build your Windows application you’re going to need a signing certificate. Microsoft has good documentation for creating the certificate which I would encourage you to follow. The important thing is to upload the certificate to the Secure files section under the Library tab on Azure DevOps and the corresponding password as a private variable. Damien Aicheh has a good tutorial on uploading certificates to Azure DevOps – don’t worry about the section on using the certificates as the Windows build template already does this for you.

Note: You also don’t need to worry about updating your package.appxmanifest so that the Publisher matches the Subject on the signing certificate. The Windows build template takes care of this too.

There are a couple of other parameters you’ll want to check off

  • uwpBuildPlatform – in this YAML this is only set to x86 for efficiency. If you’re planning to deploy your application to a range of devices, you should set this to x86|x64|ARM (this is the default value on the template so you can omit this property if you want to build for all three platforms)
  • $(windows_signing_certificate_securefiles_filename) – make sure the windows_signing_certificate_securefiles_filename variable exists in your variable group and that it’s value matches the Secure file name of the certificate (.pfx) file uploaded to the Secure files under the Library in Azure DevOps.
  • $(windows_signing_certificate_password) – make sure the windows_signing_certificate_password variable exists in your variable group and that it is password you specified when creating the certificate. This should be marked as private.

Note that the application package that we’re specifying here is an msixbundle. The Windows build pipeline has been upgraded to support both appx and msix bundles.

Windows – Deploy

The deploy stage for Windows (UWP) is

- template:  azure/mobile/[email protected]_templates
  parameters:
    stage_name: 'Deploy_Windows'
    depends_on: 'Build_Windows'
    environment_name: 'Inspector-Alpha'
    artifact_name: 'inspector-build'
    artifact_folder: 'Windows_output'
    application_package: 'Inspector-Uno-Windows.msixbundle'
    appcenter_service_connection: 'AppCenterInspectorCI'
    appcenter_organisation: 'thenickrandolph'
    appcenter_applicationid: 'Inspector-Uno-UWP-Alpha'
    appcenter_release_notes: 'Release from deploy pipeline'

Pipeline Templates v0.3.0

As promised, here’s a quick summary of the changes in v0.3.0. Technically this should have been a major version increment as there were some breaking changes but since we’re not at v1 yet, I’ve just pushed a minor update.

Breaking Changes:

  • build-xamarin-android.yml – change android_bundle_name parameter to application_package (for consistency with deploy-appcenter.yml)
  • build-xamarin-ios.yml – change ios_ipa_name parameter to application_package (for consistency with deploy-appcenter.yml)
  • build-xamarin-windows.yml – change windows_bundle_name parameter to application_package (for consistency with deploy-appcenter.yml)

Other Changes:

  • build-xamarin-android.yml – added environment variables (ANDROID_NDK_HOME, ANDROID_NDK_PATH, AndroidNdkDirectory) so that the build process can locate the NDK. This is required for Android builds that enable AOT compilation.
  • build-xamarin-android.yml: android_manifest_filename property no longer required – template will search for AndroidManifest.xml
  • build-xamarin-windows.yml: windows_package_manifest_filename property no longer required – template will search for *.appxmanifest
  • build-xamarin-ios.yml: ios_plist_filename property no longer required – template will search for Info.plist
  • build-xamarin-windows.yml: application_package supports both appxbundle and msixbundle file types. You need to make sure the filename matches the type of bundle your application is setup to create.

Deploy Xamarin.Forms Apps to App Center from a Azure Multi-Stage Pipeline using Templates and Environments that Require Manual Approval

Wow, that title’s a mouthful, and I didn’t add in there that I’ve just pushed v0.2.0 release of the Pipeline Templates repository. In this post we’re going to add stages to a YAML based Azure DevOps pipeline in order to deploy a Xamarin.Forms application to AppCenter for testing. We’ll also be using on the of … Continue reading “Deploy Xamarin.Forms Apps to App Center from a Azure Multi-Stage Pipeline using Templates and Environments that Require Manual Approval”

Wow, that title’s a mouthful, and I didn’t add in there that I’ve just pushed v0.2.0 release of the Pipeline Templates repository. In this post we’re going to add stages to a YAML based Azure DevOps pipeline in order to deploy a Xamarin.Forms application to AppCenter for testing. We’ll also be using on the of the latest features of the Azure DevOps YAML based pipelines, Environments, to insert a manual approval gate into our multi-stage pipeline.

Pipeline Templates v0.2.0

Before we get into talking about releasing apps to AppCenter I just wanted to reiterate that there is a new release of the Pipeline Templates repository with the following changes:

  • Added a new parameter, stage_name, to iOS, Android and Windows build templates. It has a default value, which matches the value previously specified on the stage element in the template, so won’t break any existing builds. This parameter can be set by the calling pipeline so that the stage is given a known name, which can then be referenced by other stages in the dependsOn element.
  • Added deployappcenter.yml template that can be used to deploy iOS, Android and Windows apps to AppCenter. For Android, if the application_package parameter is an .aab file, the calling pipeline will also need to supply keystore information. AppCenter doesn’t support .aab files, so the pipeline uses bundletool to generate and sign a fat apk, which is submitted to AppCenter.

Deploy to AppCenter

With a classic pipelines in Azure DevOps, you can setup separate build and release pipelines. However, YAML pipelines don’t differentiate between build and release pipelines; instead you can split a single pipeline into multiple stages (as we demonstrated in the previous post when we used different templates to create different stages in the build process).

To deploy apps to AppCenter we could simply create a template, similar to what we did with the build templates, that includes the tasks necessary to deploy to AppCenter, and then add new stages to our pipeline for each app we want to deploy. However, this would limit our ability to take advantage of some of the deployment specific features that are available in Azure DevOps. For this reason, the AppCenter template makes use of a deployment job in order to do the steps necessary to release an app to AppCenter.

Using the AppCenter Template

The following YAML pipeline provides an example of using the new AppCenter deployment template to deploy Windows (UWP), Android and iOS applications to AppCenter. Note that the ref element for the repository resource has been updated to point to the v0.2.0 tag.

resources:
  repositories:
    - repository: builttoroam_templates
      type: github
      name: builttoroam/pipeline_templates
      ref: refs/tags/v0.2.0
      endpoint: github_connection
  
variables:
  - group: 'Inspector XF Build Variables'
  
stages:
## Build stages excluded for brevity
- template:  azure/mobile/[email protected]_templates
  parameters:
    stage_name: 'Deploy_Windows'
    depends_on: 'Build_Windows'
    environment_name: 'Inspector-Alpha'
    artifact_name: 'inspector-build'
    artifact_folder: 'Windows_output'
    application_package: 'Inspector-XF-Windows.appxbundle'
    appcenter_service_connection: 'AppCenterInspectorCI'
    appcenter_organisation: 'thenickrandolph'
    appcenter_applicationid: 'Inspector-XF-UWP'
    appcenter_release_notes: 'Release from deploy pipeline'

- template:  azure/mobile/[email protected]_templates
  parameters:
    stage_name: 'Deploy_Android'
    depends_on: 'Build_Android'
    environment_name: 'Inspector-Alpha'
    artifact_name: 'inspector-build'
    artifact_folder: 'Android_output'
    application_package: 'Inspector-XF-Android.aab'
    appcenter_service_connection: 'AppCenterInspectorCI'
    appcenter_organisation: 'thenickrandolph'
    appcenter_applicationid: 'Inspector-XF-Android-Alpha'
    appcenter_release_notes: 'Release from deploy pipeline'
    secure_file_keystore_filename: '$(android_keystore_filename)'
    keystore_alias: '$(android_keystore_alias)'
    keystore_password: '$(android_keystore_password)'

- template:  azure/mobile/[email protected]_templates
  parameters:
    stage_name: 'Deploy_iOS'
    depends_on: 'Build_iOS'
    environment_name: 'Inspector-Alpha'
    artifact_name: 'inspector-build'
    artifact_folder: 'iOS_output'
    application_package: 'Inspector-XF-iOS.ipa'
    appcenter_service_connection: 'AppCenterInspectorCI'
    appcenter_organisation: 'thenickrandolph'
    appcenter_applicationid: 'Inspector-XF-iOS-Alpha'
    appcenter_release_notes: 'Release from deploy pipeline'

There are a couple of prerequisites that need to be setup in order for the deploy stages to work:

  • Each deploy stage specifies a depends_on property. This needs to correlate to the stage_name property specified on the corresponding build stage.
  • The artifact name, artifact folder and application_package properties need to match to the values used in the corresponding build stage.
  • A Service Connection needs to be established between Azure DevOps and AppCenter (in this case it was given the name ‘AppCenterInspectorCI’)
  • An application needs to be registered in AppCenter for each target platform. Each deploy stage needs the organisation (ie username or org_name) and applicationid (app_identifier). See the documentation on the AppCenterDistribute task for more information on how to find these values for your AppCenter apps.

Manual Approval with Environments

I mentioned earlier that using a deployment job would allow us to take advantage of deployment specific features. This is an area that’s currently under development and we’d expect to see more features lighting up in this area over time.

One feature that’s available today is the ability to add manual approval requirement to a deployment job. However, unlike in the classic pipeline where you’d create a manual approval requirement directly on the release pipeline, on a YAML pipeline you actually need to associated the deployment job with an environment and then add a manual approval on the environment.

You may have noticed that each of the deploy stages in the example YAML specified the environment_name property. This defines which environment you’re going to be deploying to. At this stage the only thing you can use this for in terms of deploying a mobile application is to require manual approval for the stage to continue. Let’s step through creating the environment and the approval requirement and you’ll see what I mean.

Create an Azure Pipelines Environment

Under the Pipelines tab, select Environments and then click the Create environment button in the center of the screen.

Next, provide a name for your environment and click Create. At this stage we don’t need to define any resources, so you can leave the default selection of “None”. The name that you specify for your environment has to match what you use as the environment_name property on the template.

Adding Manual Approval to the Environment

In order to add a manual approval requirement to an environment, simply open the environment (if you’ve just created the environment you’re already there). From the drop-down menu in the top-right corner, select Approvals and checks.

Next, click the + button in the top right corner.

Select Approvals, and then click Next

In the Approvals flyout you can specify a list of users and/or groups that need to approve a release to the environment. If you specify multiple users, each user needs to approve the release. If you specify a group, only one person in the group needs to approve the release.

In the Approvals flyout you can also specify a timeout; if the deployment isn’t approved for an environment within the timeout, the pipeline will fail.

Note: Currently there are no emails, or other notifications, sent to approvers. If you limit the timeout, once the specified period has elapsed if the environment hasn’t been approved, the pipeline fail and a notification will be sent out. The stage in the pipeline that failed can then be manually run and again, approval for the environment will be required.

Running the Build and Deploy Pipeline

Now when we run the pipeline, what we see in the portal is three different rows (one for each platform) with two stages (a build and deploy stage).

We’ve set the dependsOn element on each of the build stages to an empty array (ie []) meaning that they have no dependencies (btw the default is that stages will be done in the sequence that they appear in the YAML file, unless you indicate there should be no dependencies). Depending on how many build agents you have at your disposal the build stages may run in sequence, or in parallel.

Eventually, when each of the build stages completes, the corresponding deploy stage will light up indicating that it’s waiting for a check to be passed. There’s also a message box inserted into the interface to draw attention to the required approval.

Once all three of the build stages are complete, there are 3 approvals required; one for each of the deploy stages. The way we’ve structured the pipeline you have to approve the deployment for each platform.

If you wanted to require only a single approval for all three platforms you could inject an additional stage that was dependent on all three build stages. The approval would be required for this single stage, and then each of the subsequent deploy stages would be permitted to continue without further checks. The downside of this would be that you would have to wait for all builds to fail before you could deploy the app to any platform.

Summary

In this post we’ve looked at using a pipeline template for deploying Xamarin.Forms applications to AppCenter for testing across Windows (UWP), iOS and Android. In actual fact, and what I didn’t point out earlier, the deploy template can be used for any iOS, Android or Windows (UWP) app, not just a Xamarin.Forms application.

I also walked through setting up an environment so that you could add a manual approval step to the deployment process. Whilst the YAML pipelines don’t yet have all the features of the classic release pipeline, you can see from the way that the components connect and the UI that’s built in the portal that the foundations have been laid for future features to be built on.

Pipeline Templates: Building Xamarin.Forms Apps on Azure DevOps using Templates

One of the things I find frustrating is that for every new project we seem to have to recreate the build and release pipeline. In each case we step through the same steps, run into the same, albeit familiar, issues and end up with a pipeline that looks incredibly similar to the pipeline we setup … Continue reading “Pipeline Templates: Building Xamarin.Forms Apps on Azure DevOps using Templates”

One of the things I find frustrating is that for every new project we seem to have to recreate the build and release pipeline. In each case we step through the same steps, run into the same, albeit familiar, issues and end up with a pipeline that looks incredibly similar to the pipeline we setup for the last project we started. In this post I’m going to share the first set of Azure DevOps build templates that you can reuse in order to build your Xamarin Forms for iOS, Android and Windows.

Background

This started out as an experiment to consolidate three different build pipelines into a single build pipeline. Often for mobile applications developers resort to setting up a build pipeline for each target platform, and then potentially each target environment. For us this because untenable as we were working on a white-labelled product that would need to have a build pipeline for each offering, which would grow over time.

The first step was to leverage the ability in Azure DevOps to create multi-stage pipelines. I setup a different stage for each target platform. You might be asking, why did I set them up as different stages; couldn’t I have just created additional tasks, or perhaps even different jobs for the different platforms. Well, the nice thing about different stages is that they can use different build agents. This is of course an absolute must, since iOS needs to be built by a Mac agent, whilst Windows (ie UWP) needs to be built by a Windows build agent. I could have cheated and wrapped the Android build with either the iOS or Windows build to cut down on the build time but you’ll see in a bit why I kept them separate.

In addition, my initial goal for this experiment was to have a standard build template that we could use and that I could share with the community via my blog. However, I felt this was only a part solution – there are so many great posts out there on how to setup a build process for XYZ but they become stale the minute they’re published. What if we could create a set of templates that could be released, much like a product, and could evolve over time.

Pipeline Templates

In this post I’m introducing the Pipeline Templates github repository where I’ll be evolving build templates to help developers avoid the complexity of setting up the entire Azure DevOps, instead, focusing on building amazing applications. It’s still early days but check out the repository, watch and provide feedback so that we can evolve these templates as a community.

We’ll be starting with three templates that can be used to build a Xamarin.Forms application for iOS, Android and Windows. If we look at the basic structure of the repository, you’ll see that there’s a folder for Azure and a sub-folder for Mobile. The rationale was that over time this repository could house templates for other build platforms.

I also wanted to separate mobile app builds away from builds for the web or cloud services. However, the more I think about this the more I feel this separation is somewhat arbitrary because web apps built using SPA frameworks like React, Angular, Vue etc are similar in so many ways to mobile or desktop apps. I can imagine this structure will evolve over time – if you’re going to reference the templates in this repository, make sure you use the tags to ensure your build process doesn’t change or break as the templates evolve (more on this later).

Creating the Azure Build Pipeline

In order to use the pipeline templates you need to reference the GitHub repository as a resource within your build pipeline. I’ll walk through creating a new build pipeline but if you can add resources to an existing Yaml based pipeline using similar steps.

Start from the Pipelines tab in the Azure DevOps web portal and click the New Pipeline button (top right corner of the screen). Follow the steps to select your source code repository and pick a New pipeline template to use (I typically go with the Starter pipeline but either way we’re going to replace most of the yaml anyhow).

When you’re on the review tab, I suggest that you rename the yaml file. It’s not immediately obvious that you can change the name of the yaml file. The following image shows the default name on the left and then if you tap on the azure-pipelines.yml text you can actually edit it. On the right side of the image is the new filename and I’ve placed it in a pipelines folder to keep everything tidy in the repository.

Rather than making changes at the review tab, I just hit save (not the default “save and run” since we know the pipeline isn’t setup correctly).

Connecting to a GitHub Resource

According to the documentation you should be able to just add a GitHub resource directly to the Yaml file. However, in doing this I found that my pipeline didn’t work until I created an endpoint, similar to if you were to add other authenticated repositories as a resource.

Creating an endpoint is actually quite simple but you may well need additional permissions on your repository, depending on what access permissions you have. Click on Project Settings, in the bottom left corner of the Azure DevOps web portal, then click on the Service Connections tab. Click on the New service connection button, that’s in the top right corner of the Service connections screen. Select the GitHub connection type and then populate the New GitHub service connection flyout.

Once you click the Authorize button, you’ll most likely need to adjust the Service connection name – you’ll need this value in the next step.

The next step is to go back to your newly created pipeline (Note: if it doesn’t appear when you click on the Pipelines tab, you’ll need to switch view to All instead of Recent since you haven’t run your pipeline yet). Once you have the pipeline open, click Edit to bring up the yaml editor.

Replace the contents of the yaml file with the following:

resources:
  repositories:
    - repository: builttoroam_templates
      type: github
      name: builttoroam/pipeline_templates
      ref: refs/tags/v0.1.0
      endpoint: Pipeline-Templates

This block of yaml references the pipeline_templates repository that’s owned by builttoroam. It’s going to use the Pipeline-Templates service connection to access the repository where it’s going to look at tag v0.1.0. In the pipeline this resource can be referenced as builttoroam_templates, which is just a local name assigned to this resource by the pipeline.

Important: that you specify the ref attribute and specify a tag. If you don’t, you’ll be pointing to the latest available templates on master. Referencing master is fine as you setup your pipeline but you should change to referencing a tag release to make sure that as we change the repository, your build continues to work.

Xamarin.Forms Build Pipeline

Now that we’ve added a reference to the pipeline templates repository, we can make use of any of the templates. For example the following yaml references the ios Xamarin template.

stages:
- template:  azure/mobile/[email protected]_templates
  parameters:
    solution_filename: 'src/MyApp.sln'
    solution_build_configuration: 'Release'

In this case to access the template in the pipeline templates repository, we firstly need to use the @[local resource name] syntax and then secondly we need to provide the path to the yml file (ie azure/mobile).

This yaml snippet defines the stages for the build pipeline, including a stage that’s defined in the referenced template file (in this case build-xamarin-ios.yml). You can add additional stages by simply repeating the -template section and adjusting the name of the template.

This code snippet shows just the first two parameters being passed in. At the end of this post is a full example showing the required parameters. There are some additional parameters that can be used to adjust output file name and folder, as well as for adding custom steps to the beginning, pre and post build and at the end of the stage.

Full Example

The following is a fully worked example which includes iOS, Android and Windows. Note that there are a number of variables that are being passed into the templates. These are all defined within the Inspector XF Build Variables group, which can be defined via the Library tab in Azure DevOps.

resources:
  repositories:
    - repository: builttoroam_templates
      type: github
      name: builttoroam/pipeline_templates
      ref: refs/tags/v0.1.0
      endpoint: Pipeline-Templates
  
variables:
  - group: 'Inspector XF Build Variables'
  
stages:
- template:  azure/mobile/[email protected]_templates
  parameters:
    solution_filename: 'src/Apps/DotNet/Inspector.XF.sln'
    solution_build_configuration: 'Release'
    ios_plist_filename: 'src/Apps/DotNet/XF/InspectorXF/InspectorXF.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)'
    build_number: '$(Build.BuildId)'
    full_version_number: '$(version_prefix).$(Build.BuildId)'
    ios_signing_identity: '$(ios_signing_identity)'
    ios_provisioning_profile_id: '$(ios_provisioning_profile_id)'

- template:  azure/mobile/[email protected]_templates
  parameters:
    build_android: $(android_enabled)
    solution_filename: 'src/Apps/DotNet/Inspector.XF.sln'
    solution_build_configuration: 'Release'
    android_manifest_filename:  'src/Apps/DotNet/XF/InspectorXF/InspectorXF.Android/Properties/AndroidManifest.xml'
    build_number: '$(Build.BuildId)'
    full_version_number: '$(version_prefix).$(Build.BuildId)'
    secure_file_keystore_filename: '$(android_keystore_filename)'
    keystore_alias: '$(android_keystore_alias)'
    keystore_password: '$(android_keystore_password)'

- template:  azure/mobile/[email protected]_templates
  parameters:
    solution_filename: 'src/Apps/DotNet/Inspector.XF.sln'
    solution_build_configuration: 'Release'
    uwpBuildPlatform: '$(uwpBuildPlatform)'
    windows_package_manifest_filename:  'src/Apps/DotNet/XF/InspectorXF/InspectorXF.UWP/Package.appxmanifest'
    build_number: '$(Build.BuildId)'
    full_version_number: '$(version_prefix).$(Build.BuildId)'
    windows_cert_securefiles_filename: '$(windows_signing_certificate_securefiles_filename)'
    windows_cert_password: '$(windows_signing_certificate_password)'

There are also some secure files that have been added to the Library, which again are referenced here using a variable defined in the variable group. The process for adding secure files is that you upload the file, give it a friendly name and then assign that friendly name to a variable that’s in the variable group.

Summary

In this post I’ve given you a very quick introduction to the Pipeline Templates repository. Over the coming posts I’ll walk through some of the templates and what you can do with them in more detail. I’d love feedback on the templates – raise an issue to suggest changes on the GitHub repository and I’ll see if I can encorporate them.

Visual State Management with BuildIt.States and Uno

I’ve posted previously on using visual states in Uno and how they can be used to effectively manage the different visual layouts a page can take on. These may be changes in layout due to the application being resized, or perhaps due to different data loading states. I’ve recently created a Uno build of the … Continue reading “Visual State Management with BuildIt.States and Uno”

I’ve posted previously on using visual states in Uno and how they can be used to effectively manage the different visual layouts a page can take on. These may be changes in layout due to the application being resized, or perhaps due to different data loading states. I’ve recently created a Uno build of the BuildIt.States library, BuildIt.States.Uno. In this post I’m going to walk through using this library to help manage visual states from within your view model.

One of the topics that’s quite hot at the moment is whether XAML has had its day and whether the new coded UI techniques provide a better solution. I’m not going to go too far into this debate but one of the things I really like about XAML is the separation of the UI (declared in XAML) from its data representation (the ViewModel).

Whilst the data binding framework of both UWP and Xamarin.Forms works well for connecting properties on a ViewModel to attributes of a UI element, there is no way to connect visual states on the page, with some aspect of the ViewModel. This is where the BuildIt.States library kicks in.

Thinking about Visual States

Before we get into using the BuildIt.States library, lets start by thinking through the different states that our page can go through. For the purpose of this post we’re going to build a simple app with the following spec:

  • The app has a single page that has a button, “Load Data”
  • When the button is pressed, the button will be hidden and a loading indicator will be shown.
  • The app will attempt to load some data.
  • Loading data will randomly succeed or fail
  • When loading is complete (either succeed or fail) the loading indicator will be hidden, a message will be displayed indicating success or failure, and the Load Data button will be displayed again.
  • If the Load Data button is pressed again, the status of the previous attempt will remain on screen until the new attempt to load data has been completed.

An initial read of this spec would seem to indicate that there is a single set of states:

  • Not Loading
  • Loading
  • Loaded – Success
  • Loaded – Failed

However, when the button is pressed for the second time, there are two more states that need to be included : Loading (Loaded – Success) and Loading (Loaded – Failed).

An alternative way of thinking about the states is that there is a group of states that pertain to whether data is being loaded, and a different group of states that pertain to whether the page has data:

LoadingStates

  • NotLoading
  • Loading

These states control whether the button or the loading indicator is visible

DataStates

  • NoData
  • Data
  • DataFailedToLoad

These states control the visibility of the data, or in this case the message indicating success or failure of the data loading.

Visual States in XAML

Since the focus of this post isn’t on how to design a page in XAML I’ll skip over the steps involved in laying out the page. In summary, after creating a new Uno project (using the Uno project templates), I opened the MainPage.xaml in Visual Studio Blend. Whilst Blend is a long way from being the design-first tool it was once envisaged as, it does still have support for defining visual states, which is sorely missing in Visual Studio (and probably the only reason I still use Blend).

Using the design surface in Blend I’m able to layout the various TextBlock, Button and ProgressRing to build the simple UI for the app. I then use the States tool window to create two Visual State Groups (LoadingStates and DataStates) and the associated Visual States for showing the appropriate elements. The resulting XAML looks like the following:

<Page x:Class="BuildItWithStatesForUno.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:local="using:BuildItWithStatesForUno"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="LoadingStates">
                <VisualState x:Name="NotLoading" />
                <VisualState x:Name="Loading">
                    <VisualState.Setters>
                        <Setter Target="LoadDataButton.(UIElement.Visibility)" Value="Collapsed" />
                        <Setter Target="LoadingProgress.(UIElement.Visibility)" Value="Visible" />
                        <Setter Target="LoadingProgress.(ProgressRing.IsActive)" Value="True" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
            <VisualStateGroup x:Name="DataStates">
                <VisualState x:Name="NoData" />
                <VisualState x:Name="Data">
                    <VisualState.Setters>
                        <Setter Target="DataSuccessTextBlock.(UIElement.Visibility)" Value="Visible" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="DataFailedToLoad">
                    <VisualState.Setters>
                        <Setter Target="DataFailedTextBlock.(UIElement.Visibility)" Value="Visible" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <StackPanel HorizontalAlignment="Center"
                    VerticalAlignment="Center">
            <TextBlock x:Name="DataSuccessTextBlock"
                       Margin="20"
                       FontSize="40"
                       Foreground="Green"
                       Text="Data Loaded Successfully!"
                       TextAlignment="Center"
                       Visibility="Collapsed" />
            <TextBlock x:Name="DataFailedTextBlock"
                       Margin="20"
                       FontSize="40"
                       Foreground="Red"
                       Text="Data Failed to Load"
                       TextAlignment="Center"
                       Visibility="Collapsed" />
            <Grid Margin="50">
                <Button x:Name="LoadDataButton"
                        HorizontalAlignment="Center"
                        Click="LoadDataClick"
                        Content="Load Data"
                        FontSize="30" />
                <ProgressRing x:Name="LoadingProgress"
                              Width="50"
                              Height="50"
                              Foreground="Blue"
                              IsActive="False"
                              Visibility="Collapsed" />
            </Grid>
        </StackPanel>
    </Grid>
</Page>

Testing the Visual States

Before moving on I wanted to test that the Visual States work, so I wired up the Load Data button with an event handler so I could switch the visual states:

private async void LoadDataClick(object sender, RoutedEventArgs e)
{
    var loadTimeInMilliseconds = rnd.Next(1000, 10000);
    var success = loadTimeInMilliseconds % 2 > 0;
    VisualStateManager.GoToState(this, "Loading", true);
    await Task.Delay(loadTimeInMilliseconds);
    VisualStateManager.GoToState(this, "NotLoading", true);
    VisualStateManager.GoToState(this, success ? "Data" : "DataFailedToLoad", true);
}

Clearly this code isn’t production ready (string literals, codebehind, no error handling etc) but when I run the app, I can see the desired state changes. Here I’m just showing Android but since it’s Uno, it should work nicely on iOS, UWP and WASM too.

Visual States in Action (Gif captured and generated using Snagit from TechSmith)

ViewModel States

Now that we’ve defined the visual states, we need a way to both control and track the states in our ViewModel. In the same way that we can use data binding to update attributes of the visual elements on the page, we need a way to mirror visual states within our ViewModel. This is where we can make use of the StateManger from the BuildIt.States library.

In the following MainViewModel, a StateManager is created and setup using two different state groups. Rather than using string literals, we use an enum to define each state group. Note that each enum has a Base value, which reflects the default, or unset, state.

public enum LoadingStates
{
    Base,
    NotLoading,
    Loading
}

public enum DataStates
{
    Base,
    Data,
    DataFailedToLoad
}

public class MainViewModel : IHasStates
{
    private readonly Random rnd = new Random();

    public IStateManager StateManager { get; } = new StateManager();

    public MainViewModel()
    {
        StateManager
            .Group<LoadingStates>()
            .DefineAllStates()
            .Group<DataStates>()
            .DefineAllStates();
    }

    public async Task LoadData()
    {
        var loadTimeInMilliseconds = rnd.Next(1000, 10000);
        var success = loadTimeInMilliseconds % 2 > 0;

        await StateManager.GoToState(LoadingStates.Loading);
        await Task.Delay(loadTimeInMilliseconds);
        await StateManager.GoToState(LoadingStates.NotLoading);
        await StateManager.GoToState(LoadingStates.Loading);
        await StateManager.GoToState(success ? DataStates.Data : DataStates.DataFailedToLoad);
    }
}

I also need to update the codebehind in MainPage to create an instance of MainViewModel and then invoke the LoadData method when the Load Data button is clicked:

public sealed partial class MainPage : Page
{
    public MainViewModel ViewModel => DataContext as MainViewModel;

    public MainPage()
    {
        this.InitializeComponent();

        DataContext = new MainViewModel();
    }
    private async void LoadDataClick(object sender, RoutedEventArgs e)
    {
        await ViewModel.LoadData();
    }
}

Connecting ViewModel to Visual States

Of course, if we run the code at this point, the state tracked within the MainViewModel don’t update the UI. To complete the loop, we need to attach another StateManager to the visual states defined on MainPage, and then bind the two StateManagers so that they can remain in sync. This is all done in the MainPage codebehind.

public sealed partial class MainPage : Page
{
    public MainViewModel ViewModel => DataContext as MainViewModel;
    private IStateManager StateManager { get; } = new StateManager();

    public MainPage()
    {
        this.InitializeComponent();

        DataContext = new MainViewModel();

        StateManager
            .Group<LoadingStates>()
            .DefineAllStates(this,this.LoadingStates)
            .Group<DataStates>()
            .DefineAllStates(this, this.DataStates);

        StateManager.Bind(ViewModel.StateManager);
    }
    private async void LoadDataClick(object sender, RoutedEventArgs e)
    {
        await ViewModel.LoadData();
    }
}

And there we go – now when you run the application and press the Load Data button the LoadData method on the MainViewModel will be invoked. As the states of the MainViewModel change, the StateManager and subsequently the visual states on the MainPage are updated.

How to Support Multiple Environments in your Mobile Application?

Whether you’re developing an Android app in Kotlin, a cross-platform app in Flutter or Xamarin Forms, or an Xbox app in C#/XAML, supporting multiple environments when building an app, is just not as easy as it should be. For example the different environments might be dev, test, staging, prodution etc to align with your dev, … Continue reading “How to Support Multiple Environments in your Mobile Application?”

Whether you’re developing an Android app in Kotlin, a cross-platform app in Flutter or Xamarin Forms, or an Xbox app in C#/XAML, supporting multiple environments when building an app, is just not as easy as it should be. For example the different environments might be dev, test, staging, prodution etc to align with your dev, test and release process. Alternatively, you might have a white-labelled app that you can configure for a particular customer by adjusting some application settings. In each of these scenarios, it would be ideal to be able to deploy our application, along with a configuration, or settings, file. In this post we’re going to discuss why this isn’t possible, how this problem is typically solved, and then discuss an alternative approach to solving the problem.

Configuration Files

Before we jump in and discuss native applications, let’s take a look at a couple of scenarios where configuration files are already supported. The first example is a typical web applications that can be built once, and then released to an almost unlimited number of environments, where each one can have different settings, or attributes, applied using some form of a settings or configuration file. For example with an ASP.NET application you can specify application settings in the web.config or app.settings files. Alternatively if you’re deploying to an Azure App Service, you can configure various settings directly via the Azure portal (including overriding settings on a per-slot basis).

The use of configuration files isn’t isolated to web applications. In fact both WinForms and WPF applications can take advantage of the ConfigurationManager class in the .Net Framework to dynamically load configuration data from a file packaged alongside the application.

The introduction of application stores (eg the iOS App Store, Google Play Store and the Microsoft Store) brought with it the notion of an application package. Applications were packaged and then signed to ensure that what was received, and subsequently installed, on the device was the same package the publisher had submitted and that had been approved for distribution. None of the main stores support distributing a configuration file alongside the application, in the same way you could have done with a private distribution of a WinForms app.

Packaged Configuration Files

Given that it’s not possible to distribute a configuration file in parallel to the application, it is necessary to include configuration files within the application package. There are a couple of alternatives that you should consider when deciding on a configuration system.

Build Configuration Constants

This post by Jon that provides some background on what a build configuration is within Visual Studio and how to take advantage of it to control the behaviour of your application during development (Debug configuration) and in production (Release configuration).

Build configurations can define compilation constants that can be used to dynamically include or exclude code at compile time. The Debug build configuration typically already has the DEBUG constant defined but you can define your own. For example in the following image the DEV_ENV constant has been defined for the Debug build configuration.

In code, you can then use these constants to determine what code gets compiled. For example in the following code, when compiled with the Debug build configuration the DEV_ENV constant is defined, so the first definition of HelloText will be compiled. For all other build configurations, the DEV_ENV constant isn’t defined, so the second definition is compiled.

public static partial class Constants
{
#if DEV_ENV
    public const string HelloText = "Hello World - Dev Environment";
#else
    public const string HelloText = "Hello World";
#endif
}

You can extend this to include or exclude entire files by modifying the project file. There is no UI built into Visual Studio for doing this but the syntax of the csproj project file is relative simple, so not too hard to tweak. The following example demonstrates how to exclude two files (since all files are include by default within the project folder system), DebugConstants.cs and ReleaseConstants.cs, and then to selectively include them for the different build configurations.

<Project Sdk="Microsoft.NET.Sdk">
  ....
  <ItemGroup>
    <Compile Remove="DebugConstants.cs" />
    <Compile Remove="ReleaseConstants.cs" />
  </ItemGroup>
  <ItemGroup Condition="'$(Configuration)'=='Debug'">
    <Compile Include="DebugConstants.cs" />
  </ItemGroup>
  <ItemGroup Condition="'$(Configuration)'=='Release'">
    <Compile Include="ReleaseConstants.cs" />
  </ItemGroup>
  ....
</Project>

As you switch between Debug and Release build configurations in Visual Studio you can actually see the change in the Solution Explorer, showing which files will be included. In the following image the left screenshot of the Solution Explorer window shows that the DebugConstants.cs file has been included in the Debug configuration, whilst the right shows the ReleaseConstants.cs is included for the Release configuration.

Copy and Replace

In this post by Andrew he covers how you can include an app.settings file within your application. This is similar to the approach presented by Adam in his post on using Configuration Files in Xamarin.Forms.

The app.settings file can be replaced during the build process in order to switch between different environments. You can either choose to replace the entire app.settings file, or you can simply substitute individual key-value pairs.

Mobile Build Tools

Dan Siegel (of Prism notoriety) has developed some mobile build tools that he’s been working on to make it easier for developers to setup DevOps for mobile applications. I’d highly recommend integrating these tools into your build pipeline.

Build v Release Tasks for Multiple Environments

Ok, so before I wrap up this post I want to go back to the original premise I discussed. What I want to be able to do is to build my application once and then have different configurations for each environment. We can think of the devops for our application in two stages, Build and Release. The Build part of our process should do just that, it should build our application, and it should only have to build it once irrespective of what environment it’s going to target. The Release part of our process should augment the application configuration so that it targets the different environment.

The solutions presented so far have all resulted in the need to have different builds setup for each environments, so none of them present an ideal solution. The primary issue with applications is that the packaging format doesn’t support an external configuration file, so it’s not as simple as deploying a web application where you can simply change the configuration file.

To address this issue we need to look at how we can re-package our application during the release process, allowing us to modify a configuration file that’s included as part of the application package. More on this to come….

Do Uno Mvvm?

Last week was a huge week for the Uno platform with their inaugural Uno conference, #UnoConf. As the technology continues to mature, I’ve no doubt that Uno will become a viable solution for building applications to target all sorts of markets. This includes support being progressively added by the various Mvvm frameworks. Following my previous … Continue reading “Do Uno Mvvm?”

Last week was a huge week for the Uno platform with their inaugural Uno conference, #UnoConf. As the technology continues to mature, I’ve no doubt that Uno will become a viable solution for building applications to target all sorts of markets. This includes support being progressively added by the various Mvvm frameworks.

Following my previous posts (MVVM Navigation with Xamarin.Forms Shell and MVVM Navigation with Xamarin.Forms Shell – Part II) where I discussed a simple approach to Mvvm with Xamarin.Forms, I figured I’d so something similar with Uno.

Mvvm with Uno

Let’s get on with it and start with a new Uno project – Download and install the Uno Platform Solution Templates extension from the Visual Studio marketplace, if you haven’t already. In Visual Studio, create a new project based on the Cross-Platform App (Uno Platform) project template. I’m going to call the app DoUnoMvvm.

Creating a Class Library

We’re going to separate out our viewmodels and services into a separate library, so add a new project, DoUnoMvvm.Core, based on the Class Library (.NET Standard) project template. Delete the class1.cs and then add a reference to the class library to each of the head projects (i.e. Droid, iOS, UWAP and Wams).

Adjusting NuGet Package References

Right-click on the solution node in the Solution Explorer window and select Manage NuGet Packages for Solution. Go to the Updates tab, check the Include prerelease option and then check the box alongside the packages Uno.Wasm.Bootstrap, Uno.UI, Microsoft.NETCore.UniversalWindowsPlatform and Uno.Core (don’t check either the Logging packages). Click Update to get the latest version of the packages that are checked.

From the Browse tab on the NuGet-Solution window used in the previous step, enter BuildIt.General.Uno into the search box. Select BuildIt.General.Uno and install the packages into all five of the projects.

Mvvm Basics with ViewModelLocator

Now we should be ready to start writing some code. We’re going to keep it simple with the following steps:

  • Create ViewModelLocator class – used for serving up viewmodels and creating services as required
  • Create an instance of ViewModelLocator in App Resources, making it accessible as a static resource in XAML
  • Create MainViewModel class – the viewmodel for the existing MainPage
  • Update ViewModelLocator with a property Main that returns an instance of the MainViewModel class
  • Set the DataContext of the MainPage to use the Main property on the ViewModelLocator
  • Run the application and show data is being served by the MainViewModel.

Here we go…. firstly a new ViewModelLocator class, which is added to the DoUnoMvvm.Core project

public class ViewModelLocator
{
}

Update App.xaml to create an instance of the ViewModelLocator class

<Application
    x:Class="DoUnoMvvm.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:DoUnoMvvm"
    xmlns:core="using:DoUnoMvvm.Core"
    RequestedTheme="Light">
  <Application.Resources>
    <core:ViewModelLocator x:Key="ViewModelLocator" />
  </Application.Resources>
</Application>

Now to create the MainViewModel, also in the DoUnoMvvm.Core project. We’ll create a property, WelcomeText, that will return some data to be displayed on MainPage.

public class MainViewModel
{
    public string WelcomeText => "How well do Uno Mvvm?";
}

We need to update the ViewModelLocator class to include the Main property

public class ViewModelLocator
{
    public MainViewModel Main => new MainViewModel();
}

And use this property when setting the DataContext for MainPage. I’ve also updated the TextBlock to be data bound to the WelcomeText property.

<Page
    x:Class="DoUnoMvvm.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:DoUnoMvvm"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    DataContext="{Binding Main, Source={StaticResource ViewModelLocator}}"
    mc:Ignorable="d">

  <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <TextBlock Text="{Binding WelcomeText}" Margin="20" FontSize="30" />
  </Grid>
</Page>

Run the application and there we have it, our first data bound page

Quick Navigation using Event Mapping

That’s pretty much the basics of Mvvm. However, following my previous posts discussing navigation, I just want to demonstrate how to abstract navigation away from both the page and the viewmodel – this allows for more independent testing of viewmodels as there’s no interdependency between viewmodels. Here’s the basic process:

  • Add a new page, SecondPage, that we’re going to navigate to
  • Add a corresponding viewmodel, SecondViewModel, and property, Second, on the ViewModelLocator
  • Update SecondPage to set the DataContext to be bound to the Second property on the ViewModelLocator
  • Add a Button to MainPage that invokes a method, Next, on the MainViewModel
  • Add an event, Complete, to MainViewModel, and raise it from the Next method.
  • Add a mapping to the App.xaml.cs that navigates to SecondPage when the Complete method is raised.

And here’s the code. I’m not going to show you the initial SecondPage as it’s just generated from the template and you’ll see it later anyhow. Instead, we’ll jump to the SecondViewModel (if you’re following along you still need to add the SecondPage to the DoUnoMvvm.Shared project in the Pages folder).

public class SecondViewModel
{
    public string ProgressText => "Now you know how to navigate....";
}

Add the Second property to the ViewModelLocator

public class ViewModelLocator
{
    public MainViewModel Main => new MainViewModel();
    public SecondViewModel Second => new SecondViewModel();
}

Now back to the SecondPage and I have set the DataContext and bound the TextBlock.

<Page
    x:Class="DoUnoMvvm.Shared.Pages.SecondPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:DoUnoMvvm.Shared.Pages"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    DataContext="{Binding Second, Source={StaticResource ViewModelLocator}}"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
  <Grid>
    <TextBlock Text="{Binding ProgressText}" />
  </Grid>
</Page>

Now a Button to invoke the transition from MainPage to SecondPage

<Page
    x:Class="DoUnoMvvm.MainPage" ...
    DataContext="{Binding Main, Source={StaticResource ViewModelLocator}}" >
  <StackPanel>
    <TextBlock Text="{Binding WelcomeText}" Margin="20" FontSize="30" />
    <Button Content="Go to Second Page" Click="GoNextClick" />
  </StackPanel>
</Page>

Here we’re simply using a code behind but you could easily use a command. Unfortunately x:Bind doesn’t appear to be working with Uno yet, so you can’t simply bind the Click method to a method on the viewmodel.

public void GoNextClick(object sender, RoutedEventArgs e)
{
    (DataContext as MainViewModel)?.Next();
}

The Next method simply raises the Complete event

public class MainViewModel
{
    public event EventHandler Complete;

    public string WelcomeText => "How well do Uno Mvvm?";

    public void Next()
    {
        Complete?.Invoke(this, EventArgs.Empty);
    }
}

The final step is to add the mapping to App.xaml.cs to define what happens when the Complete event is triggered on the MainViewModel. Add the following property and method to App.xaml.cs, and update the App class to implement the IApplicationWithMapping interface (which comes from the BuildIt.General.Uno library that you should have added earlier)

public IDictionary<Type, IEventMap[]> Maps { get; } = new Dictionary<Type, IEventMap[]>();
private void MapPageTransitions()
{
    Maps.For<MainViewModel>()
        .Do(new EventHandler((s, args) => (Windows.UI.Xaml.Window.Current.Content as Frame)?.Navigate(typeof(SecondPage))))
        .When((vm, a) => vm.Complete += a, (vm, a) => vm.Complete -= a);
}

Invoke the MapPageTransitions method immediately after the Window.Current.Content property has been set equal to a new Frame. In order for the events to get correctly wired up you also need to update both MainPage and SecondPage to inherit from the MappingBasePage class.

Now when you run the application, MainPage will appear with a Button that you can click to navigate to the SecondPage.

Uno How to Mvvm!

You might be thinking…. you’ve just shown me how to do a bunch of UWP code… and that is EXACTLY the point. If you switch to the Droid or iOS or Wasm target, you can run the same application on each of those platforms with NO further code changes required. The Uno platform is about leveraging the skills you already have as a UWP (or as a Xamarin.Forms) developer, allowing you to build rich, high-quality applications for iOS, Android and Web.

Link to the source code

Content from Former Microsoft WPF and Silverlight Team Member

Someone pointed me in the direction of a series of great blog posts that have recently been migrated to a github repository. The posts are quite old, dating back to a period between 2005 and 2013 when Beatriz Stollnitz worked for Microsoft as part of the WPF and Silverlight teams. Here are the links to … Continue reading “Content from Former Microsoft WPF and Silverlight Team Member”

Someone pointed me in the direction of a series of great blog posts that have recently been migrated to a github repository. The posts are quite old, dating back to a period between 2005 and 2013 when Beatriz Stollnitz worked for Microsoft as part of the WPF and Silverlight teams. Here are the links to each of the posts – happy reading!

Update 11th August: I’ve forked the repository and have started to work my way through the sample code to a) update it to WPF on .NET 4.7.2 and b) provide a similar example for UWP + Uno (iOS, Android, WASM). Feel free to check it out and help out!

01-DataContext

02-EmptyBinding

03-GetListBoxItem

04-BindToComboBox

05-DisplayMemberPath

06-SelectedValue

07-ChangePanelItemsControl

08-BarGraph

09-CollectionViewSourceSample

10-MasterDetail

11-MasterDetailThreeLevels

12-DataBoundDialogBox

13-TemplatingItems

14-SortingGroups

15-GroupingTreeView

16-GroupByType

17-BoundListView

18-ThreeLevelMasterDetailADO

19-ObjectDataProviderSample

20-InsertingSeparators

21-CustomSorting

24-AsynchronousBinding

25-BindToEnum

26-DataTriggerSample

27-ConvertXaml

28-FilterSample

29-MultipleFilters

30-MultiBindingConverter

31-ChangesMultithreading

32-PolygonBinding

33-PolygonBinding2

34-PolygonBinding3

35-CommonQuestions

36-ADOIndependentView

37-PlanetsListBox

38-UpdateExplicit

39-TreeViewPerformancePart1

40-TreeViewPerformancePart2

41-TreeViewPerformancePart3

42-WPFPresenter

43-BindToXLinq

44-XLinqXMLMasterDetail

45-DebuggingDataBinding

46-DragDropListBox

47-ExpandTreeViewPart1

48-ExpandTreeViewPart2

49-ExpandTreeViewPart3

51-UIVirtualization

52-DataVirtualization

54-PieChartWithLabels

55-PieChartWithLabelsSilverlight

56-PieChartWithLabelsSilverlight

57-DataVirtualization

58-MultipleStyles

59-WPFCollectionViewSource

60-SLCollectionViewSource

61-OredevComputerWeekly

62-DataVirtualizationFiltering

64-DataVirtualizationFilteringSorting

66-SortingHierarchy

67-PieChartWithLabelsUpdates

69-BindRadioButtonsToEnumsPart1

70-BindRadioButtonsToEnumsPart2

71-BindRadioButtonsToEnumsPart3

72-BindRadioButtonsToEnumsPart4

73-BindRadioButtonsToEnumsPart5

74-PositioningDataBoundItems

75-SimultaneousEnableDisable

76-FocusWatcher

77-CaptureWatcher

78-BetterBindableBase

79-BooleanConverters

Using the UWP SplitView on iOS, Android and WebAssembly with Uno

In this post we’re going to cover one of the basics of app navigation which is the use of the UWP SplitView. If you’re coming from iOS and Android development you might be thinking “huh, I don’t even know what that is.” Well the good news is that it’s actually something you’re already familiar with. … Continue reading “Using the UWP SplitView on iOS, Android and WebAssembly with Uno”

In this post we’re going to cover one of the basics of app navigation which is the use of the UWP SplitView. If you’re coming from iOS and Android development you might be thinking “huh, I don’t even know what that is.” Well the good news is that it’s actually something you’re already familiar with. Whether you’re used to an app that has a master-details layout, or one that uses a burger menu to display a flyout menu, these can both be implemented using the UWP SplitView.

One thing to be aware of is that the UWP SplitView is one of the basic controls that was added early in the UWP lifecycle. Since then there have been other controls added, such as the NavigationView and the MasterDetailsView (Windows Community Toolkit) that provide extended functionality and are worth exploring depending on the requirements of your project.

New Project – Cross-Platform App (Uno Platform)

Let’s get into it – we’re going to start with creating a new project based on the latest Uno Visual Studio Extension (updated at end of July – if you’re using an older version I would recommend updating). I’m also working with the Visual Studio 2019 (16.3 preview 1.0) as this provides the best support for working with WebAssembly.

Cross-Platform App using the Uno Solution Template

Once you’ve created your solution, I would highly recommend that you a) update NuGet references and b) go through each platform and make sure you can build and run the application. However, as at the time of writing do NOT update the Microsoft.Extensions.Logging.* packages – there’s a note in the csproj files that states “Note that for WebAssembly version 1.1.1 of the console logger required.” My recommendation is to leave these packages at v1.1.1 across all projects. For the Uno libraries I pick the latest prerelease versions but be warned that this occasionally backfires as you may get an unstable version – this is why it’s important to run each platform before proceeding!!

Note: One issue I’m currently seeing in the Uno template is that it generates assets (i.e. images) with names that include the scale factor eg scale-200. This is fine for UWP but fails to build for Android. I’ve gone through and just removed the scale factor from the filenames. You’ll need to rebuild your UWP project to make sure it picks up the filename changes correctly.

Note 2: When running the WebAssembly (aka WASM) project, make sure you select “Start without Debugging” in Visual Studio.

Note 3: When running the iOS build you may need to set the Deployment Target in the Info.plist. If you’re using the latest preview of Visual Studio it will pick up iOS 12.4 on the build agent which will cause you app to fail to deploy if you haven’t set the Deployment Target.

Adding the UWP SplitView

Now that we have our new application up and running, it’s time to add the SplitView control.

Design in Blend

For this we’re going to switch over to Blend – yeh, the product still exists and you’ll see in a minute why we’re going to be using it. To make the switch, right-click on the MainPage.xaml in the shared project and select Design in Blend.

Switching to Design in Blend

Switching to Blend will take a few seconds, particularly if this is the first time you’ve ever opened Blend. You’ll also see errors as the iOS and Android projects won’t open – ignore these and don’t worry about the upgrade log that will popup (these are irritating but can be safely ignored as they won’t impact your project).

Despite selecting Design in Blend from the MainPage.xaml, once in Blend, you’ll actually need to use the Solution Explorer window to open MainPage.xaml in the designer. Once you’ve opened MainPage.xaml, Blend should feel fairly familiar, even if this is the first time you’ve used it. This is because a lot of the windows are shared with Visual Studio – the separation between the products is a mix of legacy (don’t want to disrupt the tiny percentage of developers who still use it) and the optimising that design tasks will be done using Blend.

Blend Designer

Adding the SplitView

There are a number of ways to add the SplitView control into the MainPage. If you’re familiar with writing XAML it’s probably just easiest to add the SplitView element. However, to build familiarity with Blend, let’s add the SplitView using the designer. In the Assets window, enter “splitview” to locate the SplitView control. Use the mouse to drag the SplitView down to the Objects and Timeline window at the position in the hierarchy where you want it to appear. In this case we’re going to drop it inside the existing Grid – I typically have a Grid as the root of most pages to allow setting of background and other properties using a application-wide style, independently of what content is nested within the Grid.

Adding the UWP SplitView

Adding the SplitView this way will add it as the second child of the Grid, immediately following the existing TextBlock. As we want the SplitView to take up the entire space of the page, we’re going to move the TextBlock in the main content area by dragging the TextBlock into the Grid that is nested within the SplitView. The XAML of the page should now look like the following:

<Page
    x:Class="UwpSplitViewSample.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:local="using:UwpSplitViewSample"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <SplitView>
            <SplitView.Pane>
                <Grid />
            </SplitView.Pane>
            <Grid>
                <TextBlock
                    Margin="20,20,20,20"
                    FontSize="30"
                    Text="Hello, world !" />
            </Grid>
        </SplitView>
    </Grid>
</Page>

The SplitView is made up of two distinct areas: the main content area and the pane that can be shown/hidden (i.e. the flyout). From this code we can see that the SplitView as two nested XAML elements which align to the SplitView areas. The first is the SplitView.Pane which defines the layout of the content that will appear within the pane/flyout of the SplitView. The second defines the layout of content to appear within the main content area of the SplitView, in this case a Grid with a TextBlock contained within it.

Adjusting Layout with Blend

I’ll repeat this: For those of us familiar with writing XAML, you’ll find that manually crafting XAML is by far the quickest way to add and edit elements. However, Blend provides a number of shortcuts for positioning elements that can come in handy. Some of which we’ll cover here.

Designing the SplitView Pane

For the purpose of this post, all we’re going to do is to add a StackPanel with a TextBlock and Button as children to both the SplitView.Pane and the SplitView. The TextBlock will be used to indicate what part of the SplitView it is (Pane or Content) and the Button will be used to Show and Hide the pane of the SplitView.

Let’s start with the SplitView.Pane which already has a Grid element. In the Objects and Timeline window, right-click on the Grid and select Change Layout Type, followed by StackPanel.

Change Layout Type to StackPanel

You should see the Grid change to StackPanel and remains selected in the Objects and Timeline window. Next, with the StackPanel still in focus, use the Assets window to locate the TextBlock and double-click on TextBlock to add a TextBlock as a child to the StackPanel. Repeat, this time for a Button.

We’re going to position the StackPanel in the centre of the SplitView pane. To do this, again keep the StackPanel selected in the Objects and Timeline window, we’re going to go to the Properties window and scroll down to the Layout section at set the HorizontalAlignment and VerticalAlignment properties to Center.

Centering the StackPanel

Whilst we’re using the Properties window, select the TextBlock and change the Text property to “Pane”, and then select the Button and change the Content property to “Hide Pane” (Note that unlike Xamarin.Forms that has a Text property, to set the text on a Button in UWP XAML you need to set the Content property).

The last thing we’re going to do with the XAML for the SplitView.Pane is to add an event handler to the Click event of the Button. With the Button selected in the Objects and Timeline window, go to the Properties window, and click on the lightning bold icon to switch from properties to events view. Locate the Click event and type the name of the event handler you want to create, in this case PaneButtonClick. When you press Enter the PaneButtonClick method will be created in the MainPage.xaml.cs code behind file and the XAML will be updated with the Click property.

Adding an Event Handler for the Click Event

For the moment, that’s where we’ll leave the XAML for the SplitView.Pane and we’ll implement the logic for the Click event handler shortly.

Designing the SplitView Content

The layout of the SplitView content is going to be very similar to that of the pane: A StackPanel with nested TextBlock and Button. However, we’re going to go about it slightly differently, considering the fact that we already have a TextBlock nested in a Grid in the content area. We’re going to start by wrapping the TextBlock in a StackPanel. To do this, right-click somewhere in the TextBlock on the designer and select Group Into, followed by StackPanel (this also works if you right-click on the TextBlock in the Objects and Timeline window).

After doing this the hierarchy of elements in the SplitView content will be Grid, StackPanel, TextBlock. However, if you look at the XAML you’ll notice that Blend has added some additional attributes, setting the Margin on the StackPanel and the Height and Width on the TextBlock. To remove these, we can just right-click the StackPanel and select Layout, followed by Reset All. Repeat this on the TextBlock.

From here we can repeat the remaining steps that we did for the SplitView pane:

  • Add a Button to the StackPanel
  • Set HorizontalAlignment and VerticalAlignment on the StackPanel to Center
  • Set Text on the TextBlock to “Content Area”
  • Set Content on the Button to “Show Pane”
  • Add an event handler called ShowPaneButtonClick to the Button Click event

The final XAML in MainPage.xaml should look similar to:

<Page x:Class="UwpSplitViewSample.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:local="using:UwpSplitViewSample"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">
  <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <SplitView>
      <SplitView.Pane>
        <StackPanel HorizontalAlignment="Center"
                    VerticalAlignment="Center">
          <TextBlock Text="Pane"
                     TextWrapping="Wrap" />
          <Button Click="PaneButtonClick"
                  Content="Hide Pane" />
        </StackPanel>
      </SplitView.Pane>
      <Grid>
        <StackPanel HorizontalAlignment="Center"
                    VerticalAlignment="Center"
                    Orientation="Vertical">
          <TextBlock Text="Content Area" />
          <Button Click="ShowPaneButtonClick"
                  Content="Show Pane" />
        </StackPanel>
      </Grid>
    </SplitView>
  </Grid>
</Page>

DisplayMode Property on the UWP SplitView

The UWP SplitView has two properties that are worth exploring in a bit of detail and we’ll use these to control the behaviour, or should I say the layout, of the SplitView based on screen width. Let’s start with the easy one, which is the IsPaneOpen property. As you can imagine from the name, this property indicates whether the pane of the SplitView is currently open (i.e. displayed in full, rather than hidden or in compact mode), or not.

The second property worth looking at is the DisplayMode of the SplitView, and has the following four possible values:

  • Overlay – When the pane is visible it appears as an overlay across the content area. It doesn’t affect the size of the content area when it is shown/hidden.
  • Inline – When the pane is displayed it reduces the size of the content area by the width of the pane. As the pane is shown/hidden the width of the content area shrinks and grows accordingly.
  • CompactOverlay – Same as Overlay, except when the pane isn’t open, it still takes up space on the page equal to the CompactPaneLength property. Typically the compact view would show a list of icon buttons as a summary of the options available in the pane when it’s open.
  • CompactInline – Same as Inline, except when the pane isn’t open it still takes up space on the page equal to the CompactPaneLength property.

We’ll leave the use of the CompactOverlay and CompactInline options for the moment and focus on the other two options. The Overlay option is great for when the size of the application is narrow, for example on a mobile phone. The Inline option is better suited for when the application has sufficient width to allow the pane to be shown and there still to remain sufficient content area to be useful.

Using VisualState to Set DisplayMode

With this in mind, we’re going to use visual states to set the DisplayMode property based on the width of the application. For the purpose of this post we’re going to use a split point of 900, meaning that if the width of the application is greater than 900 the DisplayMode will be set to Inline. If it’s below 900 it’ll be set to Overlay.

To begin with, we’re going to set the default value of the DisplayMode to Compact by adding DisplayMode=”Compact” to the SplitView element. Next in Blend we’re going to add two visual states via the States window. In the States window, click the Add state group button and give the state group a name, SizeStateGroup.

Adding a Visual State Group

Next, click the Add state button twice to add two visual states and name them NarrowState and WideState. The actual names of the visual state group and the states are only for your benefit at this point, so you can name them according to what makes sense for you.

Adding a Visual State

Click on the WideState and you should see a red dot appear along side the state name. This indicates that Blend is in visual state editing mode. You should also see a red border appear around the main design area. Note that when you’re in visual state editing mode in Blend, any change you make via the tool windows (eg changing a property) will be tracked against the selected visual state.

Visual State Editing Mode

In visual state editing mode for the WideState, use the Properties window to change the DisplayMode to Inline. Next click the lightning icon button, Edit Adaptive Triggers, alongside the WideState visual state.

Adding an AdaptiveTrigger

In the Collection Editor: StateTriggers, set the MinWindowHeight to 0 and the MinWindowWidth to 900. This is adding a trigger which will return true when the width of the application exceeds 900. At this point the VisualStateManager will automatically switch to the WideState, without you having to explicitly run any code. Repeat the process of setting an adaptive trigger but this time for the NarrowState and set both MinWindowHeight and MinWindowWidth to 0.

The final XAML:

<Page x:Class="UwpSplitViewSample.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:local="using:UwpSplitViewSample"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">

  <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <SplitView x:Name="splitView"
               DisplayMode="Overlay">
      <SplitView.Pane>
        <StackPanel HorizontalAlignment="Center"
                    VerticalAlignment="Center">
          <TextBlock Text="Pane"
                     TextWrapping="Wrap" />
          <Button Click="PaneButtonClick"
                  Content="Hide Pane" />
        </StackPanel>
      </SplitView.Pane>
      <Grid>
        <StackPanel HorizontalAlignment="Center"
                    VerticalAlignment="Center"
                    Orientation="Vertical">
          <TextBlock Text="Content Area" />
          <Button Click="ShowPaneButtonClick"
                  Content="Show Pane" />
        </StackPanel>
      </Grid>
    </SplitView>
    <VisualStateManager.VisualStateGroups>
      <VisualStateGroup x:Name="SizeStateGroup">
       
        <VisualState x:Name="WideState">
          <VisualState.StateTriggers>
            <AdaptiveTrigger MinWindowHeight="0"
                             MinWindowWidth="900" />
          </VisualState.StateTriggers>
          <VisualState.Setters>
            <Setter Target="splitView.(SplitView.DisplayMode)" Value="Inline" />
          </VisualState.Setters>
        </VisualState>
        <VisualState x:Name="NarrowState">
          <VisualState.StateTriggers>
            <AdaptiveTrigger MinWindowHeight="0"
                             MinWindowWidth="0" />
          </VisualState.StateTriggers>
        </VisualState>
      </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
  </Grid>
</Page>

Note: In UWP the order of the VisualState elements doesn’t matter as all the AdaptiveTriggers are evaluated and the best match is determined. For example if the application width is 1000, the conditions for the triggers for both NarrowState and WideState are met. However, the WideState is a better match since the width is > 900. For Uno (i.e. iOS, Android and WebAssembly), the order matters – the first trigger to match, wins. In the above XAML you’ll see the WideState before the NarrowState to make sure that if the application width is > 900, the WideState visual state is active.

Opening and Closing the UWP SplitView Pane

If you run the application at this point and adjust the width of the application the DisplayMode property will switch between Inline and Overlay. However, you won’t notice any change because we currently don’t have a way to open the pane. To do this we’ll add some code to the Click event handlers we created earlier, as follows:

private void PaneButtonClick(object sender, RoutedEventArgs e)
{
    splitView.IsPaneOpen = false;
}
private void ShowPaneButtonClick(object sender, RoutedEventArgs e)
{
    splitView.IsPaneOpen = true;
}

UWP SplitView In Action

With all the XAML and code in place, let’s see it in action on all various platforms.

UWP with Resizing Application
Opening the Pane on WASM
Auto-dismiss Flyout on Android

In this post we’ve covered off how easily you can build a responsive interface using Blend and the UWP SplitView. The fact that it then just works on iOS, Android and WebAssembly is a big bonus thanks to the Uno platform.

Optimising Multi-Targeting with Visual Studio Solution Filters

Over time Visual Studio has progressively improved support for solutions that have a large number of projects. MvvmCross used to be over 200 projects to handle each of the target platforms it supports. With the introduction of multi-targeted projects the number of projects dropped significantly to around 50 projects. For example, the core MvvmCross project … Continue reading “Optimising Multi-Targeting with Visual Studio Solution Filters”

Over time Visual Studio has progressively improved support for solutions that have a large number of projects. MvvmCross used to be over 200 projects to handle each of the target platforms it supports. With the introduction of multi-targeted projects the number of projects dropped significantly to around 50 projects. For example, the core MvvmCross project has 10 target framework monikers (TFM), instead of having a separate project for each framework. However, the decrease in load time (due to fewer projects) was offset by an increase in build time. Rebuilding the MvvmCross project triggers a build for each TFM, so the project is built 10 times. In this post, I’ll walk through a couple of techniques we use to reduce the build time when working with MvvmCross.

Whilst MvvmCross supports a wide range of target platforms, the reality is that when I’m making changes to MvvmCross I’ll be doing most of the work against one platform. For example being on Windows I might pick Android or Windows and use either the Playground or Playground.Forms sample apps to run up and test my changes. Since I’m working with only one platform at any given time, I don’t need every target framework to be built every time I make a change. Unfortunately there’s no way in Visual Studio to tell it not to build every target framework.

Conditional Target Frameworks

To get around the limitations of Visual Studio we’ve introduced some conditional logic into the project files that determine which TFMs are built. If we look at the top of the MvvmCross.csproj file we can see that there are a number of TFM lists.

<Project Sdk="MSBuild.Sdk.Extras">
  <PropertyGroup Condition=" '$(TargetsToBuild)' == 'All' ">
    <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">netstandard2.0;net461;Xamarin.iOS10;Xamarin.Mac20;Xamarin.TVOS10;Xamarin.WatchOS10;MonoAndroid90;tizen40;netcoreapp2.1;uap10.0.16299</TargetFrameworks>
    <TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard2.0;net461;Xamarin.iOS10;Xamarin.Mac20;Xamarin.TVOS10;Xamarin.WatchOS10;MonoAndroid90;tizen40;netcoreapp2.1</TargetFrameworks>
  </PropertyGroup>

  <PropertyGroup Condition=" '$(TargetsToBuild)' != 'All' ">
    <TargetFrameworks Condition=" '$(TargetsToBuild)' == 'Android' ">netstandard2.0;MonoAndroid90;</TargetFrameworks>
    <TargetFrameworks Condition=" '$(TargetsToBuild)' == 'Uap' ">netstandard2.0;uap10.0.16299</TargetFrameworks>
    <TargetFrameworks Condition=" '$(TargetsToBuild)' == 'iOS' ">netstandard2.0;Xamarin.iOS10</TargetFrameworks>
  </PropertyGroup>

In the Directory.build.props file for MvvmCross, which we’ll come to in a minute, we’ve defined a property called TargetsToBuild. If this is set to ‘All’, we set the TargetFrameworks property to include all the TFMs (except when the OS isn’t Windows_NT where we leave out Uap). However, if the TargetsToBuild is something different we restrict the TargetFrameworks property to the appropriate TFM (eg MonoAndroid90 when the TargetsToBuild is ‘Android’). We also include netstandard2.0 to help ensure developers don’t accidentally include platform specific code in files that are used by all platforms.

I mentioned that we define the TargetsToBuild in the Directory.build.props file. Currently what you’ll find in the Directory.build.props file in the MvvmCross repository is something that looks like the following:

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
  <TargetsToBuild>All</TargetsToBuild> 
  <!--<TargetsToBuild>Android</TargetsToBuild>-->
  <!--<TargetsToBuild>Uap</TargetsToBuild>--> 
  <!--<TargetsToBuild>iOS</TargetsToBuild>--> 
</PropertyGroup>

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

The first PropertyGroup, which is used when the build Configuration is set to Debug, allows the developer to switch TargetsToBuild by simply commenting and uncommenting the different values. For example to switch to Android I simply comment out the line with ‘All’ in it and uncomment the ‘Android’ line. Note that you have to restart Visual Studio for this change to take effect.

Solution Filters (.slnf)

Using the TargetsToBuild property works really well and significantly cuts down on the build time when doing development with MvvmCross. However, whenever I step in to work on MvvmCross I find it frustrating that Visual Studio has to load all 50+ projects when I’m not going to use them all. I’ve noticed recently that a couple of OSS projects such as Platform.Uno and Allan Richie’s Shiny have started to include solution filter files (.slnf). Solution Filter files make it easy to open a solution with only a subset of projects loaded.

There’s plenty of documentation on how to create a solution filter file, so I’m not going to cover that here. However, for MvvmCross it makes sense to have different solution filters for work with each platform. In my PR I’ve created filters for All, Android, iOS and Uap, which match the different TargetsToBuild options (the reason for this will become evident shortly).

Launch Automation

I was looking around for a way to combine solution filtering with the conditional logic for TFMs. Unfortunately there’s currently no mechanism for linking them. What this means is that developers wanting to work for particular platform have to first set the TargetsToBuild in the Directory.build.props, and then open the solution using the appropriate filter file.

I figured that if I can’t link the solution filter with a specific list of TFMs, the least I could do was to automate the process of setting the TargetsToBuild and launching the solution. For this I created a series of .bat files (yeh, old school I know): LaunchVS.All.bat, LaunchVS.Android.bat, LaunchVS.iOS.bat and LaunchVS.Uap.bat. Each of these platform specific launch files invokes “LaunchVS.bat XXX” where XXX is the corresponding platform (eg “LaunchVS.bat Android”).

The LaunchVS batch file does two things:

  • Invokes a powershell command that does a find and replace to update a property, TargetsToBuildDeveloperOverride, in the Directory.build.props file.
  • Launches Visual Studio by starting the corresponding solution filter file. Hence the reason why the names of the filter files needed to match the platforms options for TargetsToBuild.
powershell -Command "(gc Directory.build.props) -replace '<TargetsToBuildDeveloperOverride>[a-zA-Z]*</TargetsToBuildDeveloperOverride>', '<TargetsToBuildDeveloperOverride>%~1</TargetsToBuildDeveloperOverride> ' | Out-File -encoding ASCII Directory.build.props"
start MvvmCross.%~1.slnf

At this point you might be wondering why we’re updating the TargetsToBuildDeveloperOverride property instead of TargetsToBuild. The reason for this is that if we simply did a find-and-replace using the TargetsToBuild property it would replace both the value used for the Debug configuration but also the other configurations. It’s important that we don’t modify the Release configuration by accidentally changing the TFMs, since this could then accidentally be committed to the repository. To avoid this problem I created the TargetsToBuildDeveloperOverride property which is only used to set the TargetsToBuild property for the Debug configuration.

<PropertyGroup>
  <TargetsToBuildDeveloperOverride>Uap</TargetsToBuildDeveloperOverride>          
</PropertyGroup>

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

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

With these changes, all a developer has to do when working with a particular platform is to double-click the appropriate .bat file. The Directory.build.props will be updated and the corresponding .slnf file will be launched. If the updated Directory.build.props does accidentally get committed to the MvvmCross repository it won’t affect the Release build configuration. The LaunchVS batch file is clever enough to switch from one platform to another without the developer having to undo changes to the Directory.build.props file.

Hopefully in this post you’ve seen one option for switching TFMs and making it easier for developers to work with a multi-targeted solution. If you use multi-targeting in your application or library, it’s work considering adding filtering and conditional TFMs to make development easier.

Fiddler Debugging Http/Https Traffic for Xamarin iOS, Android and Windows (UWP) Applications

Debugging Http/Https Traffic using Fiddler for Xamarin iOS, Android and Windows (UWP) Applications

One of the most frustrating things as a frontend developer is when you are receiving incorrect data. You don’t know whether the problem lies with your application, or the backend services. The easiest way to validate this is to pretend to be a hacker. You can stage a man-in-the-middle attack on your own application. Debugging using tools like Fiddler or Charles can be used to inspect the traffic from your application. Unfortunately, the same effort that goes into protecting apps from such attacks, also means that it is harder for developers to setup Fiddler debugging.

In this post I’ll walk through setting up Fiddler debugging for a Xamarin.Forms application. The same basic approach will work for a native or Xamarin iOS/Android application as well. For this post I’ve created an application using the Blank Xamarin.Forms template that comes with Visual Studio 2019. I’ve selected to target all three platforms.

In the OnAppearing method in the MainPage of the Xamarin.Forms application, I’ve added some basic code to retrieve a string for a Https endpoint. We’ll use a Https endpoint on the assumption that if we can intercept Https then we can also intercept Http traffic.

protected override async void OnAppearing()
{
     base.OnAppearing();
     var client = new HttpClient();
     var users = await client.GetStringAsync("https://reqres.in/api/users?page=0");
     Debug.WriteLine(users);
}

Setting Up Fiddler Debugging

Before we get started with the individual platforms, it’s worth checking your configuration for Fiddler:

  • You need to make sure you have setup Https traffic decryption. I’m not going to repeat the documentation, so check out how to Configure Fiddler to Decrypt HTTPS Traffic.
  • You’ll also need to setup Fiddler debugging for remote traffic. This can be done by opening the Options window. Under the Connections tab, make sure the “Allow remote computers to connect” checkbox is checked. If you’re running Skype, or some other communication tools, it may have jumped onto the default port configured in Fiddler. In this case you can adjust the “Fiddler listens on port”.
Fiddler Debugging Options Window
Enabling remote connection in Fiddler

Windows (UWP)

I’ll start with Windows, because it’s relatively straight forward to get setup. With Fiddler running, you can run the UWP project from within Visual Studio and Fiddler debugging will work. Fiddler will capture the Https traffic without any further configuration or setup.

Fiddler Debugging
Capturing network traffic in Fiddler

In some cases, you need to make sure your application is added to the exemption list, so that traffic can be routed to the local machine. For more information see the blog post Revisiting Fiddler and Win8+ Immersive applications. To check this, click the WinConfig button in the top left corner of the Fiddler interface.

WinConfig
Click WinConfig to adjust the exemption list for Windows 10 apps

Locate your application and confirm that there is a check in the box next to your application. If it’s not checked, check the box, and then click Save Changes

Exemption Utility
Locate your UWP application and check the box to add your application to exemption list

One other issue I’ve seen but can’t reproduce reliably, is that sometimes when you run your application from Visual Studio it unchecks the box in the WinConfig in Fiddler. If for some reason you no longer see traffic in Fiddler for your application, go back and double check the exemption list in Fiddler.

Diagnosing Issues with Fiddler on Windows

When configuring Https traffic decryption, you’ll be prompted to install and trust the Fiddler root certificate. If you don’t accept all the prompts, Fiddler debugging for Windows applications will not work. Windows traffic debugging works without any further configuration because the Fiddler root certificate is trusted on the machine running Fiddler.

If you’re attempting to run the Windows application on a different machine than the one running Fiddler, you’ll need to install and trust the Fiddler root certificate. This is in addition to setting the remote machine as a proxy, which is a topic for another post. Navigate to http://[ipaddress]:[port] where [ipaddress] is the ip address of the machine running Fiddler and the [port] is the port number that Fiddler is listening on. Do NOT use https as this site is http only. You should see a screen similar to the following image – if you don’t, make sure you check that Fiddler is running!!

Fiddler Debugging Echo Service
Click on the FiddlerRoot certificate to install the root certificate

Click on the FiddlerRoot Certificate and install as a Trusted Certificate Authority on the Local Machine. This should allow your Windows application to trust Fiddler.

iOS Fiddler Debugging

Next up is iOS and in this case we’re going to use the iOS simulator. The same process should work with any iOS device that’s on the same local network as the machine running Fiddler. There are two steps to setup iOS for traffic debugging:

  1. Trust the Fiddler root certificate, and
  2. Set the http proxy to use the machine running Fiddler.

To setup the simulator, first launch the iOS application from Visual Studio. If you’re on Windows, this will launch the remote viewer for the simulator. Once the simulator is running, stop debugging and we’ll setup the simulator for traffic debugging.

Trusting the Fiddler Certificate

Navigate to http:[ipaddress]:[port] (eg http://192.168.1.109:8888) to load the Fiddler echo page. Then click on the Fiddler Certificate link. Follow the prompts to download and install the certificate.

Fiddler Echo Service iPhone Download Certificate iPhone Download Profile iPhone

In addition to downloading the certificate you also need to install it. Go to Settings / General / Profile and click through on the FiddlerRoot profile in order to Install it.

image image image image image image image

The Fiddler root certificate needs to be trusted as a root certificate. Go to Settings / About / Certificate Trust Settings and toggle the switch next to the FiddlerRoot certificate. Fiddler generates a certificate for each site you go to that is derived from the root certificate, so the root certificate needs to be installed as a trusted certificate.

image image image image

The only difference between a real iOS device and the simulator is that on a real iOS device you can set a network proxy. There are online tutorials, such as this one, for instructions on setting a proxy. Unfortunately, setting up a proxy this isn’t configurable on the simulator. If you want to use a proxy in the simulator, you can set the proxy on the mac running the simulator but this would affect all traffic on the mac. Alternatively, when running on the simulator, we can adjust the HttpClient to use a WebProxy using the following code:

var handler = new HttpClientHandler();
handler.Proxy = new WebProxy("192.168.1.109", 8888);
var client = new HttpClient(handler);

Running the iOS application should show network traffic in Fiddler debugging window. You should still see the returned data printed out in the Output window (ie from the Debug.WriteLine statement).

Android Fiddler Debugging

For Android I’m going to use the Android Simulator. Real devices should behave similarly, assuming they’re connected to the same local network as the machine running Fiddler.

Unlike iOS, that will use any proxy configured for the device, for Android you need to explicitly opt in to use a proxy in your code. You’ll need to use code similar to the following on both emulator and real devices:

var handler = new HttpClientHandler();
handler.Proxy = new WebProxy("192.168.1.109", 8888);
var client = new HttpClient(handler);

What’s a little scary about this code is that it “just works”. You might be thinking that this is a good thing. However, it does raise the question of how much of the system security model does the Microsoft built HttpClientHandler respect. What I would have expected is an SSL fail exception because the Fiddler root certificate isn’t trusted by the emulator. Furthermore, the application is not configured to use any user certificates.

The other thing to point out here is that you should not be using the HttpClientHandler. I’ve discussed this in my previous post on working with the HttpClient. Let’s change our code by moving it into the OnCreate method of the Android head project. We’ll also change over to using the AndroidClientHandler.

protected override async void OnCreate(Bundle savedInstanceState)
{
     ...
     var handler = new AndroidClientHandler();
     handler.Proxy = new WebProxy("192.168.1.109", 8888);
     var client = new HttpClient(handler);
     var users = await client.GetStringAsync("https://reqres.in/api/users?page=0");
     System.Diagnostics.Debug.WriteLine(users);
}

When we run the application we see the very familiar SSL handshake exception being raised, which is what we should expect. To get things to work, we now need to install the Fiddler certificate and configure the application to use user certificates.

Install the Fiddler Root Certificate

To install the Fiddler root certificate, navigate to http:[ipaddress]:[port] (eg http://192.168.1.109:8888) to load the Fiddler echo page. Click on the Fiddler Certificate link in order to download the certificate. Follow the prompts to download and install the certificate

image image image image image  image

After installing the certificate go to Setting / Security and Location / Encryption & credentials / User credentials to inspect the certificate

image  image

With the certificate installed into the user store, you need to configure the Android project to allow the use of certificates from the user store. In my post Working with Self Signed Certificates (Certificate Pinning) in Android Applications with Xamarin.Forms, I covered this in detail. The quick summary is that you need to create a network_security_config.xml file which sets the trust-anchors property (set using the debug-overrides element) to include certificates from the user store. You then need to reference this xml file from the networkSecurityConfig attribute on the application element in the AndroidManifest.xml file.

After installing the certificate and adding the network security configuration to the Android application you should now see network requests from the application appear within Fiddler debugging window.

Side Note: Links on how to setup your iOS, Android or Windows device for remote debugging

Side Note: Links on how to setup your iOS, Android or Windows device for remote debugging

Whether it be for convenience, or out of necessity, being about to set up remote debugging to your device makes for a better debugging experience. Particularly if you’re working on a Windows machine, it’s really useful to not have to sit within a meter of the machine running the build agent. 

iOS on WiFi

On iOS, you need to connect your device to Xcode this first time. Thereafter you can debug across a WiFi network. Note that there are some limitations, specifically older devices won’t support network debugging.

Configure Remote Debugging for iOS

Android ADB Configuration

For Android, you need to configure ADB to connect to your device. Note that this configuration only lasts whilst the IP address of the device is retained. If a new IP address is leased, you’ll need to reconfigure ADB to allow for remote debugging.

Windows Remote Machine Setup for Remote Debugging

With a Windows application, use the debugging tools to connect to your device and then debug using the remote machine option in Visual Studio.

Remote Machine settings

The ability to do remote debugging is particularly useful when developing an Xbox application. With the Xbox running in Dev Mode it’s possible to use the Find option to locate the address of the Xbox. Debugging an Xbox application is then the same as debugging any other Windows 10 application.

If you need to diagnose the network traffic from your application, check out the post on using Fiddler for debugging


Nick Randolph @thenickrandolph

If you need help debugging your application, contact Built to Roam

 


Self Signed iOS Certifcates and Certificate Pinning in a Xamarin.Forms application

Working with Self Signed Certificates (Certificate Pinning) in iOS Application with Xamarin.Forms

The next post in the app security series looks at working with self-signed certificates in an iOS application. Previous posts in this sequence are:

In this post we’re going to cover:

1) accessing non-secure services

2) trusting a self-signed certificate and

3) handling certificate validation.

This gives you all the options you should need when accessing which security option to use during development. It will also cover how to implement certificate pinning in the production version of your app.

Non-Secure (i.e. Http) Services

iOS is secure by default, which means that, by default, an iOS application can only connect to services over Https. The certificate will be verified against one of the well known certificate authorities. Most production services will use a certificate that has been issued by a well know certificate authority. For example, when you deploy a service to Azure App Service, the generated endpoint (eg myservices.azurewebsites.net) already has a Https endpoint with a certificate that is trusted.

In some cases being able to connect to plain-text services may useful. For example,when you’re running the services locally, or you’re attempting to connect to a service in an environment, where there is no https endpoint. In these cases, you can adjust the behaviour of the iOS application so that it can connect to a non-secure (ie http) endpoint.

Accessing Non-Secure Services

Let’s see this in action by changing our the endpoint of our service request to http://192.168.1.107:5000. The endpoint is configured for both https on port 5001 and http on port 5000. If you are trying on a new ASP.NET Core 3 project, don’t forget that the template comes with the line UseHttpRedirection in startup.cs so. If you want to expose an http endpoint you’ll need to remove that line.

image

In the iOS application if you simply change the endpoint to http://192.168.1.107:5000, the application will operate correctly. This is despite all the concern that http connections aren’t supported. This is because there’s a clear set of exceptions to the Https rule on iOS:

image

If you want to use http but instead of using an IP address (ie the exclusion we just saw) you have a domain name. Let’s try this by changing the endpoint to http://192.168.1.107.xip.io:5000. BIG shout out to the xip.io service which is super cool. You can enter any ip address before the xip.io and the returned ip address from doing a DNS lookup will be the same ip address.

image

When we run the iOS application and it attempts to make the service call we get the following exception raised within Visual Studio:

Unhandled Exception:
System.Net.WebException: <Timeout exceeded getting exception details> occurred

This isn’t very meaningful. However, in the Output window there’s much more information:T

Unhandled Exception:
System.Net.WebException: The resource could not be loaded because the App Transport Security policy requires the use of a secure connection. ---> Foundation.NSErrorException: Error Domain=NSURLErrorDomain Code=-1022 "The resource could not be loaded because the App Transport Security policy requires the use of a secure connection." UserInfo={NSLocalizedDescription=The resource could not be loaded because the App Transport Security policy requires the use of a secure connection., NSErrorFailingURLStringKey=http://192.168.1.107.xip.io:5000/api/values

Allowing Insecure Connections

This exception we can combat by including an exception in the Info.plist:

<key>NSAppTransportSecurity</key>
<dict>
   <key>NSExceptionDomains</key>
   <dict>
     <key>192.168.1.107.xip.io</key>
     <dict>
       <key>NSExceptionAllowsInsecureHTTPLoads</key>
       <true />
     </dict>
   </dict>
</dict>

Adding this to the plist will exclude the listed domain from the App Transport Security policy. Another alternative is to use the NSAllowArbitraryLoads attribute.  You should avoid using this attribute as it effectively disables the security policy for any endpoint that the app connects to.

<key>NSAppTransportSecurity</key>
<dict>
   <key>NSAllowsArbitraryLoads</key>
   <true />
</dict>

So that’s it for accessing non-secure, or Http, endpoints. Simply add the endpoint to the NSExceptionDomains element in the Info.plist file and you’re good to go.

Trusting Self-Signed Certificates

Now let’s go back to connecting to a secure endpoint. This time we’re going to keep with using a xip.io address to ensure any security policies are enforced. The secure endpoint would be https://192.168.1.107.xip.io:5001. I’ve reissued the certificate used by the ASP.NET Core application to include 192.168.1.107.xip.io in the alternative names section:

image

You would expect this to work since the endpoint domain is already listed in the Info.plist file (see earlier on doing this using the NSExceptionDomains). Unfortunately there is still an error similar to the following.

System.Net.WebException: The certificate for this server is invalid. You might be connecting to a server that is pretending to be “192.168.1.107.xip.io” which could put your confidential information at risk.

The information in this error is only partially correct. The certificate for the server is actually valid, it’s just that the app/device isn’t able to verify the integrity of the certificate.

Working with Public Keys

We need to find a way for the device to trust the certificate being returned by the server. By far the easiest way to get the certificate to be trust by applications running on an iOS device, is to install the public key for the certificate onto the device. To do this we need the public key, which we can extract from the pfx file used by the ASP.NET Core service, using the following openssl command (This site is very useful for Openssl commands):

openssl pkcs12 -in kestrel.pfx -out kestrel.pem -nodes

This extracts the public key in pem format. iOS needs der format. Luckily there’s again an openssl command for converting the files.

openssl x509 -outform der -in kestrel.pem -out kestrel.der

To get the public key to the device you can either share a hyperlink (ie upload the der file and share a link) or email the file to your self and open it on the device. My preference is just to add the file to my dropbox and then open the corresponding link using Safari on the device. Select the Direct Download option and the file downloads, extracts and attempts to installs the certificate

image

After clicking on Direct download you should see a prompt from the OS about installing a profile that has been installed from a website. We understand the risk so click on Allow. You will then see a confirmation prompt, indicating the Profile has been Downloaded.

imageimage

Installing iOS Root Certificate

It’s important to read the second prompt closely because what it’s saying is that you still need to go to Settings in order to complete the installation of the profile (which in this case is just a certificate). Open Settings / General / Profile and then select the profile for 192.168.1.107.xip.io you’ll see information about the certificate and the ability to Install (top right corner) the certificate.

image

After clicking Install and following the prompts you’ll be returned to this screen. The link to Install will change to Done. However, the certificate will be marked as Not Verified. This is because the root certificate, which in this case is the root certificate used by mkcert, is not trusted by the device.

image

Unfortunately since the certificate isn’t trusted, the application will still fail to connect to this endpoint. For the moment we’ll remove this certificate as it’s not helpful.

Trusting the Root iOS Certificate

Let’s repeat the process of installing the certificate but this time let’s install the root certificate used by mkcert. The public key can be found at C:Users[username]AppDataLocalmkcertrootCA.pem and when you attempt to install it on the device you should see something similar to

image image

Note the difference after the certificate has been installed – it is clearly marked as Verified in green.

To prevent profiles being accidentally downloaded and installed by users and for them to have full access to the device, it is necessary to manually trust certificates. The certificate settings can be found under Settings / General / About / Certificate Trust Settings. On this screen you can control which profiles (ie certificates) are fully trusted.

image

After toggling the full trust setting we’re good to try our application. We’ve not had to make any changes to the application itself. After installing the correct certificate onto the device, we’re able to connect to the secured services. Marking the root certificate as trusted on this device, also removes the need for the NSExceptionDomains section in the Info.plist file.

Validating Server Certificates (i.e. Certificate Pinning)

In this last section we’re going to look at how you can specify a certificate within the application itself. This will to allow requests to be made to the service with the self-signed certificate. Before proceeding you should removed any certificates that were previously installed.

Currently (at the time of writing) there is no way to override the certificate validation process for the out of the box NSUrlSessionHandler. There’s been work done in the past to provide a better alternative, such as the ModernHttpClient. However, most do not seem to work with self-signed certificates. They may have worked well with self-signed certificates back when the library was created but as it’s no longer maintained it appears to not support self-signed certificates.

Using the NSUrlSessionHandler

Even the sample project put together by Jonathan Peppers on SSLPinning doesn’t appear to work. Luckily with a minor tweak it’s possible to use the revised NSUrlSessionHandler to permit access to the self-signed service. Using the source code in the SSLPinning repository as the starting point, I’ve collated all the pieces of the alternative NSUrlSessionHAndler into a single file (Full source code). The main changes are:

- The addition of an UntrustedCertificate property which will accept the raw data from the .der public key

public NSData UntrustedCertificate { get; set; }


- Modification to the processing included in the DidReceiveChallenge method. This essentially installs the root certificate so that it can be trusted by the application.

if (sessionHandler.UntrustedCertificate != null)
{
   var trust = challenge.ProtectionSpace.ServerSecTrust;
    var rootCaData = sessionHandler.UntrustedCertificate;
    var x = new SecCertificate(rootCaData);

    trust.SetAnchorCertificates(new[] { x });
     trust.SetAnchorCertificatesOnly(false);
    completionHandler(NSUrlSessionAuthChallengeDisposition.PerformDefaultHandling, challenge.ProposedCredential);
}
else
{
    completionHandler(NSUrlSessionAuthChallengeDisposition.CancelAuthenticationChallenge, null);
}

In order to take advantage of the updated NSUrlSessionHandler we need to modify the setup.cs

Mvx.IoCProvider.LazyConstructAndRegisterSingleton<HttpMessageHandler, IServiceOptions>(options =>
{
     var handler = new Xamarin.SSLPinning.iOS.NSUrlSessionHandler
     {
         UntrustedCertificate = NSData.FromFile("kestrel.der")
     };
     return handler;
});

And of course we need to make sure we include the kestrel.der file in the Resources folder. Make sure the Build Action set to BundleResource.

image

Running this up and we get the response back from the service.

image

The big difference with this option is that there’s nothing outside of the application is modified. All the setting up of the trust is done within the application, making it much easier to deploy to any device without having to worry about configuring the device.

Summary of iOS Certificates

Note that we didn’t strictly do certificate pinning – we just allowed the application to connect to a self-signed endpoint. To carry out certificate pinning you can make changes to the DidReceiveChallenge method and determine whether the certificate should be trusted. The ModernHttpClient does have an implementation of a callback that the application can register for in order to determine whether the certificate, and thus the endpoind, should be trusted.

Accessing ASP.NET Core API hosted on Kestrel over Https from iOS Simulator, Android Emulator and UWP Applications.

Accessing ASP.NET Core API hosted on Kestrel over Https from iOS Simulator, Android Emulator and UWP Applications.

This post is a stepping stone to get local debugging working for a Http/2 service over Https from a Xamarin.Forms application. In my post on publishing to Azure I covered the fact that the underlying service receives a Http/1.1 connection, despite applications establishing a http/2 connection. This made it difficult to build out applications that use technology such as GRPC which rely on the http/2 protocol. To make it possible to develop both the mobile app and the services locally, we need to setup the ASP.NET Core debugging to allow the applications (ie each of the supported platforms) to connect.

This post assumes that the ASP.NET Core application is being hosted locally using Kestrel, mainly because of the limitations around http/2 (here and here). By default, when you create an ASP.NET Core application it is setup with multiple launch configurations, allowing you to switch between IIS Express, Kestrel and if you select the Docker option when creating your project, you’ll see an option to launch using Docker (as shown in the following image showing the launchSettings.json for the HeaderHelper project).

image

To switch between the different launch configurations you just need to select the right configuration from the run dropdown in Visual Studio – in this case I’ve selected the HeaderHelper option, which as you can see from the above launch configurations uses the “Project” command name that correlates to hosting using Kestrel (I know, not super obvious, right!).

image

When we run the ASP.NET Core application using the default launch configuration on Kestrel, what we see is that a command window is shown (since Kestrel is basically a console application) and then a browser window is subsequently launched. As you’d expect the out of the box experience is all good – we can see it’s launched the https endpoint and there’s the lock icon to indicate it’s trusted.

image

It’s also interesting to note that the service is returning Http/2 when according to this document (see screenshot below) the default is Http/1.1.

image

Well, it looks like the documentation hasn’t been updated in line with the latest code. If you take a look at GitHub for AspNetCore repository you can see that between the stable v2.2.4 and the v3.0.0-preview4 release there has been a change to the default value.

image

Coming back to our Kestrel hosted ASP.NET Core application, we can see that the endpoint host is localhost, which aligns with what’s in the applicationUrl property in the configuration in the launchSettings.json file. Unfortunately, localhost isn’t great when it comes to working with mobile applications as localhost doesn’t always resolve to the development machine. For example if you’re working with a real iOS or Android device, they’re most likely going to be on the same WiFi network but localhost won’t resolve to machine running the ASP.NET Core application. Similarly if you’re developing on a Windows PC and using a remote Mac to do the build and run the simulator, localhost again won’t resolve to the correct machine.

To solve this, we need to change the Kestrel configuration to expose the service in such a way that it can be accessed via the IP address of the machine where Kestrel will be running. Note that there are plenty of services such as ngrok, portmap.io and Forward which are great and easy to setup for non-secure services. However, once you get into trying to extend the configuration to support Https or Http/2 you end up needing to pay to use their premium service. These services are great if you want to extend beyond the bounds of your firewall but are overkill if all you want to do is expose your service for development purposes.

A much similar alternative is to:

– Change Kestrel to bind to all IP addresses for the host machine

– Add a firewall rule to allow in-bound connections on the posts required for the application

I’ll elaborate on these in more detail – and I’m going to do them in reverse order because the firewall rule is required in order to verify the Kestrel configuration is working when binding to the IP address.

Adding a Firewall Rule for Ports 5001 and 5000 (on Windows)

On Windows, it’s relatively straight forward to add a firewall rule that will allow inbound connections on specific ports. In this case we’re interested in adding a rule that works for ports 5000 and 5001, which are the two ports used in the applicationUrl property of launchSettings.json. Here’s the step-by-step

– Press Start key, type “Windows Defender” and click on the “Windows Defender Firewall with Advanced Security” option

– Click on “Inbound rules” in the left panel

– Click on “New rule” in the right (Actions) panel to launch the New Inbound Rule Wizard

– When prompted for the type of rule, select “Port” and click Next

– Make sure the “Specific local ports” option is selected and enter “5000-5001” (or “5000,5001”) in the text box.

– Click Next, accepting the defaults on the remaining pages of the New Inbound Rule Wizard, through to the final page where you’ll need to give the rule a name before hitting Finish.

Once you’ve created the Inbound rule, any requests made on these ports will be allowed through to whatever service is bound to those ports on your computer. You should disable this rule when you’re not making use of these ports for debugging.

Binding Kestrel to All IP Addresses

This can be done simply by changing the launchSettings.json file to replace localhost with 0.0.0.0:

image

When you rebuild (you may need to force a rebuild as sometimes the change to launchSettings.json isn’t picked up by Visual Studio) and attempt to run the application you’ll see an error page – this is because 0.0.0.0 isn’t actually a real IP address, it’s just the address used in the launchSettings to configure Kestrel to bind to all addresses.

image

If you change the address to use localhost instead of 0.0.0.0 you’ll again see that the api result is returned successfully. However, if you now use the actual IP address of the computer (in this case 192.168.1.107) you’ll see a certificate warning. Clicking the Advanced you can proceed to the site and see the result but the “Not secure” in the address bar will remain.

image

The fact that there’s a security error is going to cause a lot of issues if we don’t resolve it because none of the application platforms (ie iOS, Android, UWP) work well with Https when the certificate can’t be verified. Even if you use certificate pinning (to be covered in a future post) you’ll find it hard to configure the different platforms to work with certificates that don’t match the domain of the service.

If we take a look at the certificate being used, we can see that the Subject Alternative Name only matches with localhost.

image

Luckily this problem can be fixed by changing the certificate that is used by your ASP.NET Core application. If you’re planning on exposing your ASP.NET Core endpoint directly to the internet I would recommend getting a certificate from a well known CA. The following process can be used for setting up your service for development purposes:

If you know what you’re doing you can download the latest openssl and proceed to create your own certificates. However, this is fairly involved and a much similar way is to leverage the mkcert tool that is available at https://github.com/FiloSottile/mkcert. The steps are as follows:

– Download the latest binaries for mkcert (you might want to rename the executable from say mkcert-v1.3.0-windows-amd64.exe to mkcert.exe for convenience)

– Launch a command prompt running as Administrator

– Run “mkcert -install”. If you get an error such as “failed to execute keytool…..”  you probably didn’t read the previous step and opened a regular command prompt. You need to be running as Administrator

image

A successful install should look like:

image

The install process creates a certificate and trusts it on the local computer as a trusted certificate authority, meaning it can be used to generate other certificates.

– Run mkcert to create a certificate that you can use in your ASP.NET Core application

mkcert -pkcs12 -p12-file kestrel.pfx 192.168.1.107 localhost 127.0.0.1 ::1

image

– Copy the newly created kestrel.pfx into the ASP.NET Core project and set the Build Action to Content to make sure it gets deployed with your application.

image

– Remove the applicationUrl property from the Kestrel configuration in launchSettings.json

image

– Update the CreateHostBuilder method in program.cs to setup the Kestrel configuration. Specifically setting up both ports 5001 and 5000 to listen on Https and Http respectively. For port 5001 the kestrel.pfx certificate is used (note despite the advice we haven’t changed the password here but would recommend doing so if you’re going to use this in production)

public static IHostBuilder CreateHostBuilder(string[] args) =>
     Host.CreateDefaultBuilder(args)
         .ConfigureWebHostDefaults(webBuilder =>
         {
             webBuilder
                 .ConfigureKestrel(options =>
                 {
                     options.ListenAnyIP(5001, listenOptions =>
                     {
                         listenOptions.UseHttps(“kestrel.pfx”, “changeit”);
                     });
                     options.ListenAnyIP(5000);
                 })
                 .UseStartup<Startup>();
         });

Now when we run the ASP.NET Core application on the Kestrel hosting we can successfully navigate to the endpoint using the machines IP address.

image

Inspecting the https certificate you can see that the Subject Alternative Names include 192.168.1.107 (ie the machines IP address) and that the Certification path ends in the mkcert certificate that has been added to the trusted certificate authorities on this computer.

imageimage

Now that we’ve configured Kestrel and ASP.NET Core to play nice, what we need to do is to configure our mobile applications to connect to this service, which we’ll do in the next post.

Xamarin and the HttpClient For iOS, Android and Windows

Xamarin and the HttpClient For iOS, Android and Windows

In an earlier post that talked about using dependency injection and registering interfaces for working with Refit across both Prism and MvvmCross I had code that registered an instance of the CustomHttpMessageHandler class which internally used a HttpClientHandler for its InnerHandler. For developers who have spent a bit of time optimising their iOS, Android or Windows application, you’ll have noted that using the HttpClientHandler is generally not deemed to be best practice.  As I’m a big fan of trying to demonstrate best practices, I figured I’d expand on this a little into a post talking about the HttpClient and some of the options you have.

Firstly, a couple of bits of side reading:

What you should gather from these articles is that Microsoft is doing their best to set you up for success but not wanting to take any documentation for granted, let’s see what happens when we create a brand new Xamarin.Forms project and spin up an instance of the HttpClient. When creating the project I just picked the Blank Xamarin.Forms template and made sure that all three platforms were included. The code for creating the HttpClient just uses the zero-parameter constructor:

var client = new HttpClient();

Let’s run each platform and see what the HttpClient gives us (and at this point I haven’t updated any NuGet packages, framework versions or anything. This is just what VS2019 gives me when I create a new XF project).

UWP

image

Here we get the managed HttpClientHandler, rather than the newer (and arguably better) WinHttpHandler. Actually I didn’t find a definitive guide on which is better for UWP, although this stackoverflow post does seem to imply the WinHttpHandler would be the preferred choice, particularly if you want to leverage Http/2.

Android

image

Android is using the AndroidClientHandler which is what should give us the most up to date http experience.

iOS

image

iOS is using the NSUrlSessionHandler which is what should give us the most up to date http experience.

This all seems good (albeit that you might want to use the WinHttpHandler on UWP) so for a lot of developers they might never run into any issues. If you did want to adjust which handler is used on iOS and Android (again assuming you’re just using the HttpClient with the default constructor) you can do so via the properties dialog for the corresponding platform:

image

However, where things come unstuck is if you want to customise some of the http behaviour. In my previous post I demonstrated setting the compression flag but it could have equally been adding an additional header or changing the credentials that are sent as part of each request. In this case, it’s easy enough to use the overload of the HttpClient constructor that takes a HttpMessageHandler and use the managed HttpClientHandler implementation (as I demonstrated). As you’d have seen from the linked articles above, this isn’t ideal as the managed implementation doesn’t leverage the platform specific optimisations.

The better approach is for my application to register the platform specific handler, which in MvvmCross can be done via the Setup class (which is created by default when using MvxScaffolding):

UWP

public class Setup : MvxFormsWindowsSetup<Core.App, UI.App>
{
     protected override void InitializeIoC()
     {
         base.InitializeIoC();
        Mvx.IoCProvider.LazyConstructAndRegisterSingleton<HttpMessageHandler, IServiceOptions>(options =>
         {
return new WinHttpHandler()
             {
                AutomaticDecompression = options.Compression
             };
         });
     }
}

Android

public class Setup : MvxFormsAndroidSetup<Core.App, UI.App>
{
     protected override void InitializeIoC()
     {
         base.InitializeIoC();

 

        Mvx.IoCProvider.LazyConstructAndRegisterSingleton<HttpMessageHandler, IServiceOptions>(options =>
         {
             return new AndroidClientHandler
             {
                 AutomaticDecompression = options.Compression
             };
         });
     }
}

iOS

public class Setup : MvxFormsIosSetup<Core.App, UI.App>
{
     protected override void InitializeIoC()
     {
         base.InitializeIoC();

 

        Mvx.IoCProvider.LazyConstructAndRegisterSingleton<HttpMessageHandler, IServiceOptions>(options =>
         {
             var nsoptions = NSUrlSessionConfiguration.DefaultSessionConfiguration;
             if (options.Compression == System.Net.DecompressionMethods.None)
             {
                 nsoptions.HttpAdditionalHeaders = new NSDictionary("Accept-Encoding", "identity", new object[] { });
             }
             var handler = new NSUrlSessionHandler(nsoptions);
             return handler;
         });
     }
}

Note: for iOS the NSUrlSessionHandler enabled compression by default, so the code here illustrates how you could disable compression if you wanted by sending the identity Accept-Encoding header.

In this post I’ve shown you how you can register each of the native platform handlers to optimise the requests made when using the HttpClient. This post should be read in conjunction with my earlier post that registered the other classes necessary to create the HttpClient based on the registered handler. The only other change is that the HttpService constructor should accept an HttpMessageHandler instead of an ICustomHttpMessageHandler.

public class HttpService : IHttpService
{
     public HttpService(HttpMessageHandler httpMessageHandler, IServiceOptions options)
     {
         HttpClient = new HttpClient(httpMessageHandler as HttpMessageHandler)
         {
             BaseAddress = new Uri(options.BaseUrl)
         };
     }

 

    public HttpClient HttpClient { get; }
}

Update: It’s worth noting that the WinHttpHandler used in the UWP example isn’t part of the core framework. Instead it is accessible via the System.Net.Http.WinHttpHandler NuGet package. Visual Studio provides a handy way to find and install this package – selecting the WinHttpHandler reference (where there is a build error) and looking at the intellisense options, there is an option to Install the System.Net.Http.WinHttpHandler package.

image

Blend Still Lives

Blend Still Lives

I was surprised when editing the XAML of a UWP project that I saw a prompt to open the page in Blend – I’d all but forgotten that Blend exists.

image

On launching Blend I noticed the fancy new splash screen but is the only thing that’s been worked on? Well I currently don’t use Blend, even for UWP development, so I’m not sure why this product even exists, considering most of its features are covered by the new tool windows in Visual Studio.

I did notice that when editing the UWP page, there was at least support for Visual States. Back in its prime, Blend led the way with developer-designer engagement. Now with hot reload support in most major frameworks, the reliance on a design time experience as all but gone away. I think it’s time to say RIP to Blend and move forward with a different approach to XAML developers.