Multiple Platform Targeting in Visual Studio 2017

Suppose you are creating a library that has a central set of features and also additional features that are only available on some platforms. This means that when the project is built there are multiple assemblies created, one for each platform.

One way to achieve multi platform targeting is to create a number of separate projects, for example one for .NET Core , one for UWP, another one for .NET framework, etc. Then a shared source code project can be added and referenced by each of these individual projects; when each project is built separate binaries are produced. I’ve used this approach in the past, for example when working on FeatureToggle but is a little clunky and results in many projects in the solution.

Another approach is to have a single project that is not limited to a single platform output, but rather compiles  to multiple platform assemblies.

For example, in Visual Studio 2017, create a new .NET Core class library project called TargetingExample and add a class called WhoAmI as follows:

using System;

namespace TargetingExample
{
    public static class WhoAmI
    {
        public static string TellMe()
        {
            return ".NET Core";
        }
    }
}

After building the following will be created: "…\MultiTargeting\TargetingExample\TargetingExample\bin\Debug\netcoreapp1.1\TargetingExample.dll". Notice the platform directory “netcoreapp1.1”.

If we add a new .NET Core console app project and reference the TargetingExample project:

using System;
using TargetingExample;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(WhoAmI.TellMe());
            Console.ReadLine();
        }
    }
}

This produces the output: .NET Core

If we edit the FeatureToggle.csproj file it looks like the following (notice the TargetFramework element has a single value netcoreapp1.1):

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp1.1</TargetFramework>
  </PropertyGroup>
</Project>

The file can be modified as follows (notice the plural <TargetFrameworks>):

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>netcoreapp1.1;net461;</TargetFrameworks>
  </PropertyGroup>
</Project>

Building now produces: "…\MultiTargeting\TargetingExample\TargetingExample\bin\Debug\netcoreapp1.1\TargetingExample.dll" and  "…\MultiTargeting\TargetingExample\TargetingExample\bin\Debug\net461\TargetingExample.dll"”.

A new Windows Classic Desktop Console App project can now be added (and the .NET framework version changed to 4.6.1) and a reference to TargetingExample  added.

using System;
using TargetingExample;

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(WhoAmI.TellMe());
            Console.ReadLine();
        }
    }
}

The new console app contains the preceding code and when run produces the output: .NET Core.

Now we have a single project compiling for multiple target platforms. We can take things one step further by having different functionality depending on the target platform. One simple way to do this is to use conditional compiler directives as the following code shows:

using System;

namespace TargetingExample
{
    public static class WhoAmI
    {
        public static string TellMe()
        {
#if NETCOREAPP1_1
            return ".NET Core";
#elif NETFULL
            return ".NET Framework";
#else
            throw new NotImplementedException();  // Safety net in case of typos in symbols
#endif
        }
    }
}

The preceding code relies on the conditional compilation symbols being defined, this can be done by editing the project file once again as follows:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>netcoreapp1.1;net461;</TargetFrameworks>
  </PropertyGroup>

  <PropertyGroup Condition=" '$(TargetFramework)' == 'netcoreapp1.1' ">
    <DefineConstants>NETCOREAPP1_1</DefineConstants>
  </PropertyGroup>
  
  <PropertyGroup Condition=" '$(TargetFramework)' == 'net461' ">
    <DefineConstants>NETFULL</DefineConstants>
  </PropertyGroup>
</Project>

Now when the project is built, the netcoreapp1.1\TargetingExample.dll will return “.NET Core” and net461\TargetingExample.dll will return “.NET Framework”. Each dll has been compiled with different functionality depending on the platform.

Update: The explicit <DefineConstants> for the different platforms are not required if you want to use the defaults, e.g. "NETCOREAPP1_1", "NET461", etc as per this Twitter thread and GitHub.

FeatureToggle v4 RC1 with .NET Core Support

The pre-release RC1 version of FeatureToggle with .NET Core support is now available on NuGet.

See release notes and GitHub issues for additional background/breaking changes/limitations.

The main drive for v4 is to add initial .NET Core support.

Using Feature Toggle in a .NET Core Console App

In Visual Studio 2017, create a new .NET Core Console App and install the NuGet package. This will also install the dependent FeatureToggle.NetStandard package (.NET Standard 1.4).

Add the following code to Program.cs:

using System;
using FeatureToggle;

namespace ConsoleApp1
{

    public class Printing : SimpleFeatureToggle { }
    public class Saving : EnabledOnOrAfterDateFeatureToggle { }
    public class Tweeting : EnabledOnOrAfterAssemblyVersionWhereToggleIsDefinedToggle { }

    class Program
    {
        static void Main(string[] args)
        {
            var p = new Printing();
            var s = new Saving();
            var t = new Tweeting();

            Console.WriteLine($"Printing is {(p.FeatureEnabled ? "on" : "off")}");
            Console.WriteLine($"Saving is {(s.FeatureEnabled ? "on" : "off")}");
            Console.WriteLine($"Tweeting is {(t.FeatureEnabled ? "on" : "off")}");


            Console.ReadLine();
        }
    }
}

Running the application will result in an exception due to missing appSettings.config file: “System.IO.FileNotFoundException: 'The configuration file 'appSettings.json' was not found and is not optional.“ By default, FeatureToggle will expect toggles to be configured in this file, add an appSettings.json and set its Copy To Output Directory to “Copy if newer” and add the following content:

{
  "FeatureToggle.Printing": "true",
  "FeatureToggle.Saving": "01-Jan-2014 18:00:00",
  "FeatureToggle.Tweeting": "2.5.0.1" // Assembly version is set to 2.5.0.0
}

Running the app now result in:

Printing is on
Saving is on
Tweeting is off

Using Feature Toggle in an ASP.NET Core App

Usage in an ASP.NET core app currently requires the configuration to be provided when instantiating a toggle, this may be cleaned up in future versions. For RC1 the following code shows the Startup class creating a FeatureToggle AppSettingsProviderand and passing it the IConfigurationRoot from the startup class.

public void ConfigureServices(IServiceCollection services)
{
    // Set provider config so file is read from content root path
    var provider = new AppSettingsProvider { Configuration = Configuration };

    services.AddSingleton(new Printing { ToggleValueProvider = provider });
    services.AddSingleton(new Saving { ToggleValueProvider = provider });

    // Add framework services.
    services.AddMvc();
}

The appSettings would look something like the following:

{
  "FeatureToggle.Printing": "true",
  "FeatureToggle.Saving": "false",
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Warning"
    }
  }
}

As an example of using this configuration check out the example project on GitHub, in particular the following:

https://github.com/jason-roberts/FeatureToggle/blob/master/src/Examples/AspDotNetCoreExample/Models/Printing.cs

https://github.com/jason-roberts/FeatureToggle/blob/master/src/Examples/AspDotNetCoreExample/Models/Saving.cs

https://github.com/jason-roberts/FeatureToggle/blob/master/src/Examples/AspDotNetCoreExample/ViewModels/HomeIndexViewModel.cs

https://github.com/jason-roberts/FeatureToggle/blob/master/src/Examples/AspDotNetCoreExample/Controllers/HomeController.cs

https://github.com/jason-roberts/FeatureToggle/blob/master/src/Examples/AspDotNetCoreExample/Views/Home/Index.cshtml

https://github.com/jason-roberts/FeatureToggle/blob/master/src/Examples/AspDotNetCoreExample/Startup.cs

https://github.com/jason-roberts/FeatureToggle/blob/master/src/Examples/AspDotNetCoreExample/appsettings.json

New Pluralsight Course: Reducing C# Code Duplication in Azure Functions

Azure Functions allow small discrete pieces of code to execute in response to an external stimulus such as a HTTP request, message queue message, new blob data, etc.

Just because functions are easy to create (even writing and testing code right in the Azure Portal) doesn’t mean good practices such as avoiding code duplication can be abandoned.

My new Pluralsight course Reducing C# Code Duplication in Azure Functions shows some ways to reduce or remove code duplication both in a single Function App and across apps.

Free eBook C# 7.0: What’s New Quick Start Complete

My new free eBook “C# 7.0: What’s New Quick Start” is now complete and available for download.

C# 7.0: What’s New Quick Start eBook Cover Image

The book covers the following:

  • Literal Digit Separators and Binary Literals
  • Throwing Exceptions in Expressions
  • Local Functions
  • Expression Bodied Accessors, Constructors and Finalizers
  • Out Variables
  • By-Reference Local Variables and Return Values
  • Pattern Matching
  • Switch Statements
  • Tuples

You can download it for free or pay what you think it is worth.

Happy reading!

New Pluralsight Course: Azure Function Triggers Quick Start

Azure Functions allow small discrete pieces of code to execute in response to an external stimulus such as a HTTP request, message queue message, new blob data, etc. They can also be triggered manually from within the Azure Portal or set to execute on a specified schedule.

My new Azure Function Triggers Quick Start  Pluralsight course shows how to get up to speed quickly with each of the function trigger types such as:

  • Manual Triggers
  • Azure Queue Storage Triggers
  • Blob Triggers
  • Timer Triggers
  • HTTP Triggers
  • Webhook Triggers
  • Service Bus Triggers
  • Event Hub Triggers

You can check out the course here.

Creating Versioned APIs with Azure Functions and Proxies

One of the interesting possibilities with the (currently in preview) Azure Function Proxies is the ability to create HTTP APIs that can be versioned and also deployed/managed independently.

For example, suppose there is a API that lives at the root “https://dctdemoapi.azurewebsites.net/api". We could have multiple resources under this root such as customer, products, etc.

So to get the product with an id of 42 we’d construct: “https://dctdemoapi.azurewebsites.net/api/products?id=42”.

If we wanted the ability to version the API we could construct “https://dctdemoapi.azurewebsites.net/api/v1/products?id=42” for version 1 and “https://dctdemoapi.azurewebsites.net/api/v2/products?id=42” for version 2, etc.

Using proxies we can use the format “https://dctdemoapi.azurewebsites.net/api/[VERSION]/[RESOURCE]?[PARAMS]”

Now we can create 2 proxies (for example in an Azure Function called “dctdemoapi”) that forwards the HTTP requests to other Function Apps (in this example dctdemoapiv1 and dctdemoapiv2).

Screenshots of the proxies are as follows:

Azure Function proxy settings for API version 1

Azure Function proxy settings for API version 2

And the respective proxies.json config file:

{
    "proxies": {
        "v1": {
            "matchCondition": {
                "route": "api/v1/{*restOfPath}"
            },
            "backendUri": "https://dctdemoapiv1.azurewebsites.net/api/{restOfPath}"
        },
        "v2": {
            "matchCondition": {
                "route": "api/v2/{*restOfPath}"
            },
            "backendUri": "https://dctdemoapiv2.azurewebsites.net/api/{restOfPath}"
        }
    }
}

Notice in the proxy config the use of the wildcard term “{*restOfPath}” – this will pass the remainder of the path segments to the backend URL, for example “products”, meaning a request to “https://dctdemoapi.azurewebsites.net/api/v1/products?id=42” will be sent to “https://dctdemoapiv1.azurewebsites.net/api/products?id=42”; and “https://dctdemoapi.azurewebsites.net/api/v2/products?id=42” will be sent to “https://dctdemoapiv2.azurewebsites.net/api/products?id=42”.

Now versions of the API can be updated/monitored/managed/etc independently because they are separate Function App instances, but code duplication is a potential problem; common business logic could however be compiled into an assembly and referenced in both Function Apps.

To jump-start your Azure Functions knowledge check out my Azure Function Triggers Quick Start Pluralsight course.

Using Azure Functions and Microsoft Flow to Send Notifications for NuGet Package Downloads

One of the NuGet packages I maintain is approaching 100,000 downloads. I thought it would be nice to get a notification on my phone when the number of downloads hit 100,000.

To implement this I installed the Flow app on my iPhone, wrote an Azure Function that executes on a timer, and calls into Flow.

Creating a Flow

The first step is to create a new Microsoft Flow that is triggered by a HTTP Post being sent to it.

The flow uses a Request trigger and a URL is auto generated by which the flow can be initiated.

The second step is the Notification action that results in a notification being raised in the Flow app for iOS.

Azure Function calling Microsoft Flow

Creating an Azure Function with Timer Trigger

Now that there is a URL to POST to to create notifications, a timer-triggered Azure Function can be created.

This function screen scrapes the NuGet page (I’m sure there’s a more elegant/less brittle way of doing this) and grabbing the HTML element containing the total downloads. If the total number of downloads >= 100,000 , then the flow URL will be called with a message in the body. The timer schedule runs once per day. I’ll have to manually disable the function once > 100,000 downloads are met.

The function code:

using System.Net;
using HtmlAgilityPack;
using System.Globalization;
using System.Text;

public static async Task Run(TimerInfo myTimer, TraceWriter log)
{       
    try
    {
        string html = new WebClient().DownloadString("https://www.nuget.org/packages/FeatureToggle");

        HtmlDocument doc = new HtmlDocument();
        doc.LoadHtml(html);     
                            
        HtmlNode downloadsNode = doc.DocumentNode
                                    .Descendants("p")
                                    .First(x => x.Attributes.Contains("class") &&  
                                                x.Attributes["class"].Value.Contains("stat-number"));                        

        int totalDownloads = int.Parse(downloadsNode.InnerText, NumberStyles.AllowThousands);
        
        bool thresholdMetOrExceeded = totalDownloads >= 1; // 1 for test purposes, should be 100000

        if (thresholdMetOrExceeded)
        {
            var message = $"FeatureToggle now has {totalDownloads} downloads";

            log.Info(message);

            await SendToFlow(message);            
        }        
    }
    catch (Exception ex)
    {
        log.Info(ex.ToString());
        await SendToFlow($"Error: {ex}");
    }
}

public static async Task SendToFlow(string message)
{
    const string flowUrl = "https://prod-16.australiasoutheast.logic.azure.com:443/workflows/[redacted]/triggers/manual/paths/invoke?api-version=2015-08-01-preview&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=[redacted]";

    using (var client = new HttpClient())
    {
        var content = new StringContent(message, Encoding.UTF8, "text/plain");
        
        await client.PostAsync(flowUrl, content);
    }
}

Manually running the function (with test threshold of 1 download) results in the following notification on from the Flow iOS app:

This demonstrates the nice thing about Azure Functions, namely that it’s easy to throw something together to solve a problem.

iOS Flow App Notification

To jump-start your Azure Functions knowledge check out my Azure Function Triggers Quick Start Pluralsight course.

Cross Function App Proxies and Proxy Request Parameters in Azure Functions

In a previous article we saw how to get started with Azure Function Proxies. In addition to creating proxy URLs for HTTP functions in the same Function App, it is also possible to specifiy a backend URL that exists in a different Function App (or even a URL somewhere completely different). This essentially allows a single (HTTP) API for clients to use, that behind the scenes routes traffic to multiple Function Apps to enable  the decomposing of a single large Function App into multiple smaller ones. This also enables isolation between different Function Apps for deployment, testing, management, monitoring, etc..

For example, suppose we have an HTTP function defined in a Function App called myazurecloudfunctions that GETs a customer using an id querystring parameter; a request for customer 42 would look like the following (with function authorization enabled):

https://myazurecloudfunctions.azurewebsites.net/api/LoadCust?id=42&code=SKmM3Hr0IFEdJp9QnQ5ztu/mClYScUxjdjQw4TvSQ9OXm0xrgXQUpg==

In another Function App called dontcodetireddemos a proxy can be created to enable the following URL to be used instead:

https://dontcodetireddemos.azurewebsites.net/customer/42?code=SKmM3Hr0IFEdJp9QnQ5ztu/mClYScUxjdjQw4TvSQ9OXm0xrgXQUpg==

Notice in this proxied URL the id 42 is specified in the URL, not as a querystring parameter. This is another feature of Azure Function Proxies that  allows request parameters to be mapped from the proxy URL to the backend URL. In this example the proxy route template is “customer/{custId}” and the backend is set to “https://myazurecloudfunctions.azurewebsites.net/api/LoadCust?id={custId}” – notice the token {custId}.

The proxies.json configuration is:

{
    "proxies": {
        "CustomerGet": {
            "matchCondition": {
                "route": "customer/{custId}",
                "methods": [
                    "GET"
                ]
            },
            "backendUri": "https://myazurecloudfunctions.azurewebsites.net/api/LoadCust?id={custId}"
        }
    }
}

And a screenshot of the configuration:

Azure Function Proxy settings

To jump-start your Azure Functions knowledge check out my Azure Function Triggers Quick Start Pluralsight course.

Azure Functions Proxies Preview

Azure functions allow the creation of HTTP-triggered code. A new feature to Functions is the ability to define proxies. (Note: at the time of writing this feature is in preview)

For example a function can be created that responds to HTTP GETs to retrieve a customer as the following code demonstrates:

using System.Net;

public class Customer 
{
    public int Id {get; set;}    
    public string Name {get; set;}
}

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
    // error checking omitted

    string id = req.GetQueryNameValuePairs()
        .FirstOrDefault(q => string.Compare(q.Key, "id", true) == 0)
        .Value;

    return req.CreateResponse(HttpStatusCode.OK, GetById(int.Parse(id)));
}

public static Customer GetById(int id)
{
    // simulate fetching a customer, e.g. from Table storage, db, etc.
    return new Customer
        {
            Id = id,
            Name = "Amrit"
        };
}

This function (called “GetCustomer”) could be available at the following URL (with function authorization enabled): https://dontcodetireddemos.azurewebsites.net/api/GetCustomer?id=42&code=rEQKsObGFDRCCiVuLhOnZ1Rdfn/XGgp5tfC1xHrhAqxvWqzHSQszCg==

Notice in the URL the name of the function is prefixed with “api” i.e. “api/GetCustomer”

Calling this URL would result in the following JSON being returned:

{
  "Id": 42,
  "Name": "Amrit"
}

Function proxies allow the “api” part to be replaced, for example to create a URL:  “https://dontcodetireddemos.azurewebsites.net/customer?id=42&code=rEQKsObGFDRCCiVuLhOnZ1Rdfn/XGgp5tfC1xHrhAqxvWqzHSQszCg==”

Azure Function Proxy

This proxy defines a name “CustomerGet”, a route template of “customer”, the actual URL to maps to “https://dontcodetireddemos.azurewebsites.net/api/GetCustomer”, and limited to only GET HTTP verbs.

If we had another function e.g. called PostCustomer we could also setup a POST proxy for that function:

Azure Function Proxy

Now we can issue both POSTs and GETs to the customer “resource” at: https://dontcodetireddemos.azurewebsites.net/customer (code key omitted).

Using Kudu for example, we can see a proxies.js file created in the wwwroot folder with the following contents:

{
    "proxies": {
        "CustomerGet": {
            "matchCondition": {
                "route": "customer",
                "methods": [
                    "GET"
                ]
            },
            "backendUri": "https://dontcodetireddemos.azurewebsites.net/api/GetCustomer"
        },
        "CustomerPost": {
            "matchCondition": {
                "route": "customer",
                "methods": [
                    "POST"
                ]
            },
            "backendUri": "https://dontcodetireddemos.azurewebsites.net/api/PostCustomer"
        }
    }
}

To jump-start your Azure Functions knowledge check out my Azure Function Triggers Quick Start Pluralsight course.

 

Push Notifications and Buttons with Microsoft Flow: Part 3

In part 1 we crated a Flow to enable/disable the sending of push notifications and in part 2 we created an Azure Function to generate random phrases of positivity.

In this third and final part of this series we’ll go and create the second Flow that sends the push notifications to the phone.

This Flow will be automatically triggered every 15 minutes by using a Recurrence action followed by an action to get the blob content containing whether or not we should send a notification as the following screenshot shows:

Running a Microsoft Flow every 15 minutes

Now that we have the blob content, we can examine it in an If condition. If the content of the blob is “enabled” we can continue the Flow:

If condition in Microsoft Flow

If the condition is satisfied (“IF YES”) two actions are  performed: the first an HTTP GET to the Azure function, the second a push notification action that as content uses what was returned in the body of the HTTP GET. Notice in the HTTP GET, the URI includes the function authorization key as a query string parameter.

flow4

After saving the new Flow, we can head back to the Flow app and hit the button to enable “positivity pushes”:

Microsoft Flow iOS app button

Then every 15 minutes (until we turn them off by hitting the button again) we’ll get a positivity notification on the phone:

iOS push notifications from Microsoft Flow