Daniel Vaughan

free range code

Unit Testing Cross-Platform .NET Apps

clock March 9, 2017 14:26 by author Daniel Vaughan

I'm working on a new, soon to be released, open-source project. Without giving too much away, it's a set of libraries for UWP, WPF, and Xamarin.* apps.

Now if you're using VS 2017 to build your apps, the tooling story for cross-platform apps has become ever more unified. You have .NET Standard for cross-platform application logic; much less friction than PCLs in my view. Cross-platform Unit testing, however, is a bit of a missing piece at the moment. The Microsoft testing framework exists for both UWP and WPF apps, and has the same API surface. If you find yourself wanting to use a shared project to test an Android app though, you'll soon discover that you need to venture away from the Microsoft testing framework to NUnitLite, which has a similar but incompatible API surface.

So, how do you reuse your unit tests across platform specific test projects. Here's how I did.

Create a Shared project in your solution and reference it from each platform specific test project. 

With type aliases, we can use the same attribute names across each platform.

At the top of each unit test class add the following:

#if __ANDROID__

using NUnit.Framework;

using TestClass = NUnit.Framework.TestFixtureAttribute;

using TestMethod = NUnit.Framework.TestAttribute;

#else

using Microsoft.VisualStudio.TestTools.UnitTesting;

#endif

Decorate each test class with the [TestClass] attribute and each test method with a [TestMethod] attribute. I chose the Microsoft attributes since I'm guessing the test infrastructure will unify towards the Windows tooling.

Fortunately, the Assert class, which is the foundation of both the Microsoft and NUnitLite testing, exists in both frameworks. The API surface is almost identical. There are, however, some differences. To avoid litering my code with pre-processor directives, I created a class named AssertX, which attempts to unify those differences. See below.

using System;

#if __ANDROID__
using NUnit.Framework;
#else
using Microsoft.VisualStudio.TestTools.UnitTesting;
#endif

namespace Foo.Testing
{
    class AssertX
    {
        public static void IsInstanceOfType(object instance, Type type)
        {
#if __ANDROID__
            Assert.IsInstanceOfType(type, instance);
#else
            Assert.IsInstanceOfType(instance, type);
#endif
        }

    }
}

 

So there you have it, a couple tips to keep your cross-platform unit test projects DRY as a bone.

 



UWP Form Validation with the Calcium MVVM Framework

clock January 21, 2017 23:33 by author Daniel Vaughan

I've published a new CodeProject article covering form validation in Calcium. While the form validation APIs are cross-platform, this article is specific to UWP. It could however be applied to WPF, Xamarin Android & iOS.

In this article, you see how to perform both synchronous and asynchronous form validation in a UWP app. You see how set up viewmodel properties for validation, both declaratively and programmatically. You look at how to define your validation logic in a single method or to have the validation logic reside on a remote server. You also see how to use UWP dictionary indexers to bind form validation errors to a control in your view. You then explore the innerworkings of the validation system, and see how Calcium decouples validation from your viewmodel so that it can be used with any object implementing INotifyPropertyChanged.

Read more https://www.codeproject.com/Articles/1166859/UWP-Form-Validation-with-Calcium



UWP Alien Sokoban - Part 1

clock October 13, 2016 17:47 by author Daniel Vaughan

 

I had some fun over the last couple of days writing up an article on using UWP to create a sokoban puzzle game implementation.

You can find the first part over at http://www.codeproject.com/Articles/1138427/UWP-Sokoban-Part

Below is a screen shot of the game.

 

 

 

 



Using .resw files with UWP Compiled Bindings

clock September 30, 2015 18:36 by author Daniel Vaughan

Introduction

With UWP and WinRT, Microsoft introduced a new means for localizability, which differs significantly from the method employed in Silverlight and .NET desktop apps. The new model allows you to localize all aspects of your UI, including element dimensions, using x:Uid element identifiers. The downside is that you don’t get the nice static typing that used to come for free with resx code generation.

In this article you see how to generate classes from a .resw file, which provides both static accessors and instance accessors that are compatible with UWP’s compiled bindings; enabling compile time validation of your resource names. You see how the UI updates automatically when the current locale changes. You also learn how to plug in a StringParserService to enable text to be dynamically inserted into strings, as well as embedding references to other resource strings.

NOTE: The UWP and WinRT localizability model is the recommended approach by Microsoft and the techniques described in this article should not be seen as a replacement for the new model, but rather they complement it and can be used in conjuntion with it. See the MSDN resources for more information on preparing a UWP app for localization.

Generating Resource Classes with T4

Using T4 to generate strongly typed resources is not new. I used T4 to provide a unified way to access localized resource for Xamarin.Android and Xamarin.iOS. In fact there already exists a Visual Studio extension for generating statically typed resources for UWP. The beauty of that tool is that there’s no need to refresh the T4 template. So why this article? Well, we go further than just static static accessors. We look at supporting compiled bindings. It’s as easy as adding a T4 template in your project. You also see how to leverage a StringParserService that allows you to add dynamic content to your localized strings.

To begin, we create a T4 template that generates two classes. The first contains the static accessors that you’d ordinarily see when using a .resx file. The second class contains non-static properties that we can bind to. See Listing 1. The output of the T4 template contains a RetrieveString method that uses the ResourceLoader instance to retrieve the resource value according to the current locale.

The T4 template generates an accessor for each resource in the .resw file, beginning with AppTitle. Take note of the RetrieveString method. This is an extensibility point for the script. You can plug in some logic to add a custom step to the string retrieval process. I chose to parse the string through Calcium’s IStringParserService. The IStringParserService allows you to register custom tags that are able to married with actions to retrieve things like the current time. But, the main reason I enjoy using the IStringParserService is that it allows me to combine resource strings; embedding one string within another. For example, if there is a resource named AppTitle, the StringParserService allows you to compose another resource like so:

Welcome to ${l:AppTitle}

The ${...} format is just a convention to distinguish the tag among other content.

At run-time the StringParserService recursively resolves embedded tags within the resources. We cover how to register a converter with the StringParserService at the end of the article.

Listing 1: Generated Strings class

public partial class Strings 
{
    static readonly ResourceLoader resourceLoader; 
 
    static Strings() 
    {
    try
        {
            resourceLoader = ResourceLoader.GetForViewIndependentUse("Strings");
        }
    catch (TypeInitializationException ex)
        {
    throw new Exception("Unable to locate the .resw file with the name: Strings.resw", ex);
        }
    }
    public static string AppTitle => RetrieveString("AppTitle");
    public static string Commands_Register => RetrieveString("Commands_Register");
    // ...
    static IStringParserService stringParserService;
    static readonly object stringParserServiceLock = new object();
    static string RetrieveString(string resourceKey)
    {
    string resourceString = resourceLoader.GetString(resourceKey);
    if (resourceString == null || !resourceString.Contains("${"))
        {
    return resourceString;
        }
    if (stringParserService == null)
        {
    lock (stringParserServiceLock)
            {
    if (stringParserService == null)
                {
                    stringParserService = Dependency.Resolve<IStringParserService, StringParserService>();
                }
            }
        }
    var result = stringParserService.Parse(resourceString);
    return result;
    }
}

The second class output by the T4 template is a BindableStrings class. This class allows for data-binding in your UI. See Listing 2. BindableStrings leverages the Strings class to expose the resources outside of a static context. The class constructor subscribes to the MapChanged event of the ResourceContext’s QualifierValues object. When the current locale changes, the MapChanged event is raised; allowing you to update the bindings and trigger a repopulation of localized strings in the UI. It’s elegant to see a change in locale reflected immediately in the UI, without requiring an app restart. By the way, you can see this in an action with Surfy Browser for Windows Phone when you change the language in the options screen.

NOTE: The HandleMapChanged method invokes the call to TriggerUpdateBindings if the event is raised by a thread that is not the apps UI thread. If you don’t do this, an AccessViolationException can ensue.

Listing 2: BindableString Class

public class BindableStrings : INotifyPropertyChanged
{
    public BindableStrings()
    {
    var resourceContext = ResourceContext.GetForViewIndependentUse();
        resourceContext.QualifierValues.MapChanged += HandleMapChanged;
    }
    void HandleMapChanged(IObservableMap<string, string> sender, IMapChangedEventArgs<string> @event)
    {
    var dispatcher = Windows.UI.Xaml.Window.Current.Dispatcher;
    if (dispatcher.HasThreadAccess)
        {
            TriggerUpdateBindings();
        }
    else
        {
            dispatcher.RunAsync(CoreDispatcherPriority.Normal, TriggerUpdateBindings);
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;
    public void TriggerUpdateBindings()
    {
    var handlers = PropertyChanged;
    if (handlers != null)
        {
            handlers(this, new PropertyChangedEventArgs(string.Empty));
        }
    }
    public string AppTitle => Strings.AppTitle;
    public string Commands_Register => Strings.Commands_Register;
    //...
}

The BindableStrings class contains all the localizable string properties of the Strings class, however they aren’t static, which allows binding to them.

There are a number of ways to consume the BindableStrings class. You can declare a BindableString instance as an application resources with your App.xaml file or an associated resource dictionary, which allows you to bind to it using the Binding markup extension. If, however, you wish to benefit from static verification during compilation, a BindableStrings object needs to be accessible via a property (direct or nested) of your page or control. Here's one way to do it:

Declare an instance of your BindableStrings object within your view or viewmodel or base viewmodel, like so:

readonly static BindableStrings strings = new BindableStrings();

Expose the instance as a public property:

public BindableStrings Strings => strings;

You can then place a compiled binding in your XAML, using the x:Bind markup expression, like so:

<TextBlock Text="{x:Bind ViewModel.Strings.AppTitle}" />

The T4 template allows you to override the default namespace of the resulting class. You must provide the path of the .resw file. See Listing 3. If a namespace is not provided, the T4 template uses a call to Host.ResolveParameterValue(...) to resolve the value. By convention it removes the last segment of namespaces ending in Localizability. You want the localizable string classes available broadly within your app, not hidden in a child namespace.

The T4 template’s GetResourceKeys method extracts the resource keys from the .resw file. These are turned into accessors in the resulting classes.

Listing 3. String.tt T4 Template

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Xml" #>
<#@ assembly name="System.Xml.Linq" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Xml.Linq" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="Microsoft.CSharp" #>
<#@ output extension=".cs" #>
<#
    var reswPath = @"../Localizability/ResourceFiles/en-US/Strings.resw";
    string namespaceOveride = null;
     
    var provider = new CSharpCodeProvider();
    var className = provider.CreateEscapedIdentifier(
        Path.GetFileNameWithoutExtension(Host.TemplateFile));
 
    Directory.SetCurrentDirectory(Host.ResolvePath(""));
    if (File.Exists(reswPath))
    { 
    int lastIndexOfSlash = reswPath.LastIndexOf("/") + 1;
    int lastIndexOfDot = reswPath.LastIndexOf(".");
    string reswFileNameWithoutExtension 
            = reswPath.Substring(lastIndexOfSlash, lastIndexOfDot - lastIndexOfSlash);
#>
using Windows.ApplicationModel.Resources;
using Windows.ApplicationModel.Resources.Core;
using Windows.Foundation.Collections;
using System;
using System.ComponentModel;
using Outcoder;
using Outcoder.Services;
 
namespace <#= GetNamespace(namespaceOveride) #> 
{
    public partial class <#= className #> 
    {
    static readonly ResourceLoader resourceLoader; 
 
    static <#= className #>() 
        {
    try
            {
                resourceLoader = ResourceLoader.GetForViewIndependentUse("<#= reswFileNameWithoutExtension #>");
            }
    catch (TypeInitializationException ex)
            {
    throw new Exception("Unable to locate the .resw file with the name: <#= reswFileNameWithoutExtension #>.resw", ex);
            }
        }
<#
    foreach (string name in GetResourceKeys(reswPath).Where(n => !n.Contains(".")))
        {
#>        public static string <#= provider.CreateEscapedIdentifier(name) #> => RetrieveString("<#= name #>");
<#
        }
#>
    static IStringParserService stringParserService;
    static readonly object stringParserServiceLock = new object();
    static string RetrieveString(string resourceKey)
        {
    string resourceString = resourceLoader.GetString(resourceKey);
    if (resourceString == null || !resourceString.Contains("${"))
            {
    return resourceString;
            }
    if (stringParserService == null)
            {
    lock (stringParserServiceLock)
                {
    if (stringParserService == null)
                    {
                        stringParserService = Dependency.Resolve<IStringParserService, StringParserService>();
                    }
                }
            }
    var result = stringParserService.Parse(resourceString);
    return result;
        }
    }
    public class Bindable<#= className #> : INotifyPropertyChanged
    {
    public BindableStrings()
        {
    var resourceContext = ResourceContext.GetForViewIndependentUse();
            resourceContext.QualifierValues.MapChanged += HandleMapChanged;
        }
    void HandleMapChanged(IObservableMap<string, string> sender, IMapChangedEventArgs<string> @event)
        {
    var dispatcher = Windows.UI.Xaml.Window.Current.Dispatcher;
    if (dispatcher.HasThreadAccess)
            {
                TriggerUpdateBindings();
            }
    else
            {
                dispatcher.RunAsync(CoreDispatcherPriority.Normal, TriggerUpdateBindings);
            }
        }
    public event PropertyChangedEventHandler PropertyChanged;
    public void TriggerUpdateBindings()
        {
    var handlers = PropertyChanged;
    if (handlers != null)
            {
                handlers(this, new PropertyChangedEventArgs(string.Empty));
            }
        }
<#
    foreach (string name in GetResourceKeys(reswPath).Where(n => !n.Contains(".")))
        {
    string propertyName = provider.CreateEscapedIdentifier(name);
#>        public string <#= propertyName #> => Strings.<#= propertyName #>;
<#
        }
#>
    }
}
<#
    }
    else
    {
    throw new FileNotFoundException(); 
    }
#>
<#+
    string GetNamespace(string namespaceOveride)
    {
    if (!string.IsNullOrWhiteSpace(namespaceOveride))
        {
    return namespaceOveride;
        }
    string result = Host.ResolveParameterValue("directiveId", "namespaceDirectiveProcessor", "namespaceHint");
    if (result.EndsWith(".Localizability"))
        {
            result = result.Substring(0, result.LastIndexOf(".Localizability"));
        }
    return result;
    }
 
    static IEnumerable<string> GetResourceKeys(string filePath)
    {
    var doc = XDocument.Load(filePath);
    return doc.Root.Elements("data").Select(e => e.Attribute("name").Value);
    }
#>

Registering a Converter with the StringParserService

Calcium’s StringParserService allows you to register IConverter objects. An IConverter is used to resolve text when a string is being parsed. IConverter has a single Convert method and accepts an object parameter. The following shows the LocalizableResourcesConverter that allows you to embed resources within other resources:

public class LocalizableResourcesConverter : IConverter
{
    public object Convert(object fromValue)
    {
        ArgumentValidator.AssertNotNull(fromValue, "fromValue");
    var result = Strings.ResourceManager.GetObject(fromValue.ToString());
    return result;
    }
}

In your app’s startup code, I instantiate the StringParserService and then register the LocalizableResourcesConverter. I then register the StringParserService with the IoC container, as show:

var stringParserService = new StringParserService();
IConverter converter = new LocalizableResourcesConverter();
stringParserService.RegisterConverter("l", converter);
Dependency.Register<IStringParserService>(stringParserService);

You could choose a fancier way of resolving the IConverters at run-time, but I haven’t seen cause for that. You’ll find more example of IoC registerations in the Calcium template apps.

Conclusion

This article demonstrated how to generate a classes from a .resw file, which provides both static accessors and instance accessors that are compatible with UWP’s x:Bind markup extension; enabling compile time validation of your resource names. You also saw how to plug-in a StringParserService to enable text to be dynamically inserted into strings as well as resource strings with their own embedded references to other resource strings.

Download the sample code for this project: UwpLocalizabilityExample.zip (105.95 kb) 

Alternatively, to ensure you have the most up-to-date version of the code, I recommend that you download the source from the Calcium repository and locate the Calcium.Installation.Uwp solution within the repository.

 



Automaticly Incrementing the Package.appxmanifest Version Number

clock August 28, 2015 01:01 by author Daniel Vaughan

When submitting a UWP or WinRT app to the Windows Store, you must increase the Version number of the package or the submission will fail. 

It can be a chore to manually update the version number. One solution is to use a pre-build event action that executes a Powershell script that increments the version number automatically. In this post you see how a Powershell script reads the Package.appmanifest file for your project, replaces the Version attribute, and overwrites the Package.appmanifest file; leaving your package ready to submit without having to modify the manifest file manually.

To get started, create a Powershell file in the root of your project. You can call it IncrementVersion.ps1. See Listing 1. The Powershell script expects the path to the working directory to be provided. When testing the script in the Windows PowerShell ISE, the working directory is set to the script root. When, however, the script is run via a Pre-build event, the working directory is supplied using a parameter.

As you may be aware, a package’s version number has four parts: major, minor, build, and revision. IncrementVersion.ps1 uses the same Major version that exists in the Package.appxmanifest file; it doesn’t change it. It does, however, overwrite the minor, build, and revision numbers. It calculates the new values using the current year as the minor part; the day of the year as the build part; and the minute of the day as the revision part. You can, of course, invent a different scheme for calculating the version number.

The script uses a multi-line regular expression to locate the Identity element in the Package.appxmanifest file. The content of the file is read using a Get-Content (gc) commandlet. The result of the Get-Content is an array, which is collapsed to a string using the -join command. A string is needed to allow the regular expression to search correctly across multiple lines. The regex object uses a callback to construct the new Identity element; replacing the version parts with the calculated values. Using a callback, rather than a simple call to -replace, provides more control over dynamically calculating the new version number parts.

Listing 1. IncrementVersion.ps1

param([string]$workingDirectory = $PSScriptRoot)
Write-Host 'Executing Powershell script IncrementVersion.ps1 with working directory set to: ' $workingDirectory
Set-Location $workingDirectory

$inputFileName = 'Package.appxmanifest'
$outputFileName = $PSScriptRoot + '/Package.appxmanifest';

$now = Get-Date
$versionMinor = $now.Year
$versionBuild = $now.DayOfYear
$versionRevision = ($now.Hour * 60) + $now.Minute

$content = (gc $inputFileName) -join "`r`n"

$callback = {
  param($match)
    [string]$versionMajor = $match.Groups[2].Value
    $match.Groups[1].Value + 'Version="' + $versionMajor + '.' + $versionMinor + '.' + $versionBuild + '.' + $versionRevision + '"'
}

$identityRegex = [regex]'(\<Identity[^\>]*)Version=\"([0-9])+\.([0-9]+)\.([0-9]+)\.([0-9]+)\.*\"'
$newContent = $identityRegex.Replace($content, $callback)

[io.file]::WriteAllText($outputFileName, $newContent)

To have Visual Studio execute this script whenever you build your app, you need to modify the project’s Build Events. Open your project’s properties by selecting the project node and hitting Alt+Enter. Add the following Pre-build event:

Powershell -File "$(ProjectDir)IncrementVersion.ps1" "$(ProjectDir)\"

Notice the trailing back slash? It's important because the $(ProjectDir) macro includes a trailing back slash, but it ends up escaping the quote, which breaks the script. Hence another slash is appended, which is escaped by the output of the macro. A fiddly little detail.

If you receive a message informing you that execution of scripts is disabled you may want to try modifying the execution policy by issuing the following command at a Powershell command line prompt with Admin privileges.

set-executionpolicy remotesigned

You may also choose to set the execution policy to unrestricted. Be warned, however, that there are security implications for allowing any script to execute on your computer.

That’s it. If all is in place, then your Package.appxmanifest file should update automatically, and you won’t need to manually change the version number each time you submit your app to the Windows Store.

Download the sample project: IncrementVersionNumber.zip (125.99 kb)



Order the Book

Ready to take your Windows Phone development skills to the next level? My book is the first comprehensive, start-to-finish developer's guide to Microsoft's Windows Phone 8. In it I teach through complete sample apps that illuminate each key concept with fully explained code and real-world context. Windows Phone 8 Unleashed

Bio

Daniel VaughanDaniel Vaughan is co-founder and president of Outcoder, a Swiss software and consulting company dedicated to creating best-of-breed user experiences and leading-edge back-end solutions, using the Microsoft stack of technologiesin particular UWP, WPF, and Azure. 

Daniel is a eight-time Microsoft MVP, with experience across a wide range of industries including finance, e-commerce, and digital media. 
Daniel is the author of Windows Phone 8 Unleashed and Windows Phone 7.5 Unleashed, published by SAMS.

Daniel is a Silverlight and WPF Insider, a Microsoft Azure advisor, a member of the WPF Disciples, and a member of the Microsoft Developer Guidance Advisory Council.
Daniel also sits on the advisory board of PebbleAge, a Swiss Financial Software company.

While originally from Australia and the UK, Daniel is currently based in Zurich Switzerland. 

Daniel is the developer behind several acclaimed Windows Phone apps including Surfy Browser, Farlight, Intellicam and Splashbox; and is the creator of a number of popular open-source projects including the Calcium and Clog.

Daniel also manages the Windows Phone Experts group on LinkedIn; a group that has over 4500 independent developers, Microsoft employees, and Windows Phone enthusiasts.

Microsoft MVP logo Disciple
WPF and Silverlight Insiders

Groups

Windows Phone Experts Windows 10 Experts
LinkedIn Group

Sign in