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.

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!

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.

Push Notifications and Buttons with Microsoft Flow: Part 2

In part 1 we created a Flow to toggle the sending of push notifications on and off by storing the configuration in Azure blob storage.

Now that we have a way of enabling/disabling notifications we can start to build the second Flow.

Before jumping into the Flow designer, we need to consider how to generate random positivity phrases and how to integrate this into the second Flow. One option to do this is to create a simple Azure Function with an HTTP trigger. The Flow can then use an HTTP action to issue a GET to the server that will return the string content to be sent via push notifications.

In the Azure Portal function editor a new function can be created with a HTTP trigger configured to GET only as the following screenshot shows:

Creating an Azure Function with a HTTP trigger

Notice in the preceding screenshot the authorization level has bee set to “function”. This means the key needs to be provided when the function is called.

We can now write some code in the function code editor window as follows:

using System.Net;

public static HttpResponseMessage Run(HttpRequestMessage req, TraceWriter log)
{
    log.Info("C# HTTP trigger function processed a request.");

    string phrase = GeneratePhrase();

    return req.CreateResponse(HttpStatusCode.OK, phrase);
}

public static string  GeneratePhrase()
{
    var phrases = new string[]
    {
        "Don't worry, be happy :)",
        "All is well",
        "Will it matter in 100 years?",
        "Change what you can, don't worry about what you can't"
    };

    var rnd = new Random();
    
    return phrases[rnd.Next(phrases.Length)];
}

Azure Function code editor window

Clicking the Run button will test the function and we can see the random phrases being returned with a 200 status as shown in the following screenshot:

Azure function HTTP test output

In the final part of  this series we’ll go and create the second Flow that uses this function and the configuration value created in the previous article to actually send random positivity push notifications on a 15 minute schedule.

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

Custom Session Logging in Marten

Marten is a .NET document database library that uses an underlying PostgreSQL database to store objects as JSON. The library has a variety of features that allow the logging of SQL statements issued to the underlying PostgreSQL including previewing LINQ query SQL statements.  One of the other logging features available allows custom logging to be created for individual session operations such as successfully issued database SQL commands, failed commands, and changes that were saved. There are also numerous other logging/extension points that can be utilized such as logging schema change SQL and automatically using a logger for all sessions.

The following code shows a console application that writes two customers and then retrieves them:

using System;
using static System.Console;
using Marten;

namespace MartenFKDemo
{
    class Customer
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public override string ToString() => $"{Id} {Name}";
    }

    class Program
    {
        static void Main(string[] args)
        {
            const string connectionString = "host = localhost; database = OrderDb; password = g7qo84nck22i; username = postgres";

            var store = DocumentStore.For(connectionString);

            WriteLine("Creating new customer");
            using (var session = store.OpenSession())
            {
                session.Store(new Customer {Name = "Amrit"}, new Customer {Name = "Sarah"});

                session.SaveChanges();
            }


            WriteLine("All customers:");
            using (var session = store.QuerySession())
            {
                foreach (Customer customer in session.Query<Customer>())
                {
                    WriteLine(customer);
                }
            }
            
            ReadLine();
        }
    }
}

Running the application results in the following output:

Creating new customer
All customers:
9001 Amrit
9002 Sarah

To create a custom session logger, the IMartenSessionLogger interface can be implemented, a simple version that logs to the console is shown as follows:

class ColorConsoleLogger : IMartenSessionLogger
{
    public void LogSuccess(Npgsql.NpgsqlCommand command)
    {
        ForegroundColor = ConsoleColor.Green;

        WriteLine(command.CommandText); // additional properties (e.g. SQL parameters) are available
    }

    public void LogFailure(Npgsql.NpgsqlCommand command, Exception ex)
    {
        ForegroundColor = ConsoleColor.Red;

        WriteLine(command.CommandText); // additional properties (e.g. SQL parameters) are available
        WriteLine(ex);
    }

    public void RecordSavedChanges(IDocumentSession session, IChangeSet commit)
    {
        ForegroundColor = ConsoleColor.Gray;

        foreach (object insertedItem in commit.Inserted) //  updated/deleted are also available
        {
            WriteLine(insertedItem);
        }
    }
}

To configure individual sessions to use this logger, the sessions Logger property can be set as the following modified code demonstrates:

ResetColor();
WriteLine("Creating new customer");
using (var session = store.OpenSession())
{
    // Set logger for this session
    session.Logger = new ColorConsoleLogger();

    session.Store(new Customer {Name = "Amrit"}, new Customer {Name = "Sarah"});

    session.SaveChanges();
}

ResetColor();
WriteLine("All customers:");
using (var session = store.QuerySession())
{
    // Set logger for this session
    session.Logger = new ColorConsoleLogger();

    foreach (Customer customer in session.Query<Customer>())
    {
        ResetColor();
        WriteLine(customer);
    }
}

This produces the following output:

Creating new customer
select public.mt_upsert_customer(doc := :p0, docDotNetType := :p1, docId := :p2, docVersion := :p3);select public.mt_upsert_customer(doc := :p4, docDotNetType := :p5, docId := :p6, docVersion := :p7);
13001 Amrit
13002 Sarah
All customers:
select d.data, d.id, d.mt_version from public.mt_doc_customer as d
13001 Amrit
13002 Sarah

screenshot of Marten custom logger

To learn more about the document database features of Marten check out my Pluralsight courses: Getting Started with .NET Document Databases Using Marten and Working with Data and Schemas in Marten or the documentation.

Previewing the Generated PostgreSQL SQL for a Query in Marten

Marten is a .NET document database library that uses an underlying PostgreSQL database to store objects as JSON. The library has a variety of features that allow the logging of SQL statements issued to the underlying PostgreSQL database in addition to being able to do things such as get the PostgreSQL query plan for a given LINQ query.

One simple way to get the generated SQL for a Marten LINQ query is to use the ToCommand() extension method.

As an example, suppose we are developing some query code as follows (this code uses the Include method to include the related documents in a single database round-trip):

Customer customer = null;

List<Order> orders = session.Query<Order>()
                            .Include<Customer>(joinOnOrder => joinOnOrder.CustomerId, includedCustomer => customer = includedCustomer)
                            .Where(x => x.CustomerId == 4001).ToList();

If we want to get an idea of what SQL Marten will generate for this LINQ query, we can change the code as shown in the following:

Customer customer = null;

IQueryable<Order> orders = session.Query<Order>()
                                  .Include<Customer>(joinOnOrder => joinOnOrder.CustomerId, includedCustomer => customer = includedCustomer)
                                  .Where(x => x.CustomerId == 4001);

// Get the SQL command that will be issued when the query executes
NpgsqlCommand cmd = orders.ToCommand();

// Output some selected command info
Console.WriteLine(cmd.CommandText);

foreach (NpgsqlParameter parameter in cmd.Parameters)
{
    Console.WriteLine($"Parameter {parameter.ParameterName} = {parameter.Value}");
}

// Ensure included customer variable is populated
List<Order> orderResults = orders.ToList();

Console.WriteLine(customer.Name);
foreach (Order order in orderResults)
{
    Console.WriteLine($" Order {order.Id} for {order.Quantity} items");
}

Running this preceding code  results in the following console output:

select d.data, d.id, d.mt_version, customer_id.data, customer_id.id, customer_id.mt_version from public.mt_doc_order as d INNER JOIN public.mt_doc_customer as customer_id ON d.customer_id = customer_id.id where d.customer_id = :arg0
Parameter arg0 = 4001
Sarah
 Order 3001 for 42 items
 Order 4001 for 477 items
 Order 5001 for 9 items

To learn more about the document database features of Marten check out my Pluralsight courses: Getting Started with .NET Document Databases Using Marten and Working with Data and Schemas in Marten.

Retrieving Raw JSON Data in Web API with Marten

Marten is a .NET document database library that uses an underlying PostgreSQL database to store objects as JSON.

Ordinarily, Marten takes care of retrieving the JSON from the database and deserializing it into an object. We can however instruct Marten to perform a query to retrieve document(s) and not perform the deserialization, instead giving us the JSON as it appears in the underlying PostgreSQL record. If we are exposing documents via a Web API, this feature can be taken advantage of to reduce some processing overhead on the server.

As an example, we could have the following Customer document defined:

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

    // etc.
}

And in a CustomersController we could start with a method as follows to add Customers to the database:

public void Post(Customer customer)
{
    // no validation

    // DocumentStore would normally be created only once in app, e.g. via IOC singleton 
    using (var store = DocumentStore.For(ConnectionString))
    {
        using (var session = store.LightweightSession())
        {
            session.Store(customer);
            session.SaveChanges();
        }
    }
}

To get all Customers, the following method could be written:

// GET api/customers
public IEnumerable<Customer> Get()
{
    // DocumentStore would normally be created only once in app, e.g. via IOC singleton 
    using (var store = DocumentStore.For(ConnectionString))
    {
        using (var session = store.QuerySession())
        {
            return session.Query<Customer>();
        }
    }
}

The preceding method however incurs the additional overhead of Marten deserializing the database JSON into Customer objects, only to be serializing it again back into JSON on the way out of the API.

When creating the LINQ query, the Marten ToJsonArray() method can be added to instruct Marten to simply return the JSON directly from the database.

We can then modify the Get method as follows:

public HttpResponseMessage Get()
{
    // DocumentStore would normally be created only once in app, e.g. via IOC singleton 
    using (var store = DocumentStore.For(ConnectionString))
    {
        using (var session = store.QuerySession())
        {
            string rawJsonFromDb = session.Query<Customer>().ToJsonArray();

            var response = Request.CreateResponse(HttpStatusCode.OK);
            response.Content = new StringContent(rawJsonFromDb, Encoding.UTF8, "application/json");
            return response;
        }
    }
}

We could also write the parameterized Get method and use Marten’s AsJson() method to get the JSON string for the individual Customer document as  in the following code:

public HttpResponseMessage Get(int id)
{
    // DocumentStore would normally be created only once in app, e.g. via IOC singleton 
    using (var store = DocumentStore.For(ConnectionString))
    {
        using (var session = store.LightweightSession())
        {
            var rawJsonFromDb = session.Query<Customer>().Where(x => x.Id == id).AsJson().FirstOrDefault();

            if (string.IsNullOrEmpty(rawJsonFromDb))
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }

            var response = Request.CreateResponse(HttpStatusCode.OK);
            response.Content = new StringContent(rawJsonFromDb, Encoding.UTF8, "application/json");
            return response;
        }
    }
}

To learn more about the document database features of Marten check out my Pluralsight courses: Getting Started with .NET Document Databases Using Marten and Working with Data and Schemas in Marten.

New Pluralsight Course: Working with Data and Schemas in Marten

Marten is a .NET document database library to allows objects to be stored, retrieved, and queried as documents stored as JSON in an underlying PostgreSQL database. This new course is a follow-on from the previous Getting Started with .NET Document Databases Using Marten course, if you’re new to Marten I’d recommend checking out the previous course first before continuing with this new one.

Among other topics, this new course covers how to log/diagnose the SQL that is being issued to PostgreSQL; how to enable offline optimistic concurrency; bulk document inserts; a number of ways to improve query performance; and the customization of database schema objects.

You can check out the new course on the Pluralsight site.

Screen Scraping As A Service with Azure Functions in 5 Mins

If you have some data in a web page but there is no API to get the same data, it’s possible to use (the often brittle and error prone) technique of screen scraping to read the values out of the HTML.

By leveraging Azure Functions, it’s trivial (depending on how horrendous the HTML is) to create a HTTP Azure Function that loads a web page, parses the HTML, and returns the data as JSON.

The fist step is to create an HTTP-triggered Azure Function:

Creating a new Azure Function with a HTTP Trigger

To help with the parsing, we can use the HTML Agility Pack NuGet package. To add this to the function, create a project.json file and add the NuGet reference:

Using NuGet Packages in Azure Functions

{
  "frameworks": {
    "net46":{
      "dependencies": {
        "HtmlAgilityPack": "1.4.9.5"
      }
    }
   }
}

Once this file is saved, the NuGet package will be installed and a using directive can be added: using HtmlAgilityPack;

Now we can download the required HTML page as a string, in the example below the archive page from Don’t Code Tired, use some LINQ to get the post titles, and return this as the HTTP response. Correctly selecting/parsing the required data from the HTML page is likely to be the most time-consuming part of the function creation.

The full function source code is as follows. Notice that there’s no error checking code to simplify the demo code. Screen scraping should usually be a last resort because of its very nature it can often break if the UI changes or unexpected data exists in the HTML. It also requires the whole page of HTML be downloaded which may raise performance concerns.

using System.Net;
using HtmlAgilityPack;

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
    HttpClient client = new HttpClient();

    string html = await client.GetStringAsync("http://dontcodetired.com/blog/archive");    

    HtmlDocument doc = new HtmlDocument();
    doc.LoadHtml(html); 
    
    var postTitles = doc.DocumentNode
                        .Descendants("td")
                        .Where(x => x.Attributes.Contains("class") && x.Attributes["class"].Value.Contains("title"))
                        .Select(x => x.InnerText);

    return req.CreateResponse(HttpStatusCode.OK, postTitles);
}

We can now call the function via HTTP and get back a list of all Don’t Code Tired article titles as the following Postman screenshot shows:

Using Postman to call Azure Functions

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

Reducing Database Round Trips With Included Documents in Marten

Marten is a .NET document database library that uses an underlying PostgreSQL database to store objects as JSON.

In the  previous article (Enforcing Referential Integrity Between Documents with Marten and PostgreSQL) we saw how we can enable referential integrity between documents and the example showed a Customer document may have many related Order documents.

When creating LINQ queries with Marten, one feature is the idea of included documents. So for example we might want to get a Customer document and all the Order documents for that customer.

We could write some code as follows to get Customer 4001 and a list of their Orders:

// 2 trips to database, 2 queries
using (var session = store.QuerySession())
{
    Customer customer = session.Query<Customer>()
                               .Single(x => x.Id == 4001);

    Console.WriteLine(customer.Name);

    IEnumerable<Order> orders = session.Query<Order>()
                                       .Where(x => x.CustomerId == 4001);

    foreach (var order in orders)
    {
        Console.WriteLine($" Order {order.Id} for {order.Quantity} items");
    }
}

The preceding code however will result in two round trips to the database which may result in a performance problem:

select d.data, d.id, d.mt_version 
from public.mt_doc_customer as d 
where d.id = 4001 LIMIT 2
select d.data, d.id, d.mt_version
from public.mt_doc_order as d 
where d.customer_id = 4001

 

The .Include() extension method of Marten allows a single round trip to be made to the database that executes a SQL join and returns both the Customer data and Orders.

The following code shows how to accomplish this, notice that we need to declare an “output” variable to hold the “included” customer.

// 1 trip to database, 1 query
using (var session = store.QuerySession())
{                
    Customer customer = null;

    List<Order> orders = 
         session.Query<Order>()
                .Include<Customer>(joinOnOrder => joinOnOrder.CustomerId, includedCustomer => customer = includedCustomer)
                .Where(x => x.CustomerId == 4001)
                .ToList();


    Console.WriteLine(customer.Name);

    foreach (var order in orders)
    {
        Console.WriteLine($" Order {order.Id} for {order.Quantity} items");
    }  
}

The preceding code now only makes a single request to the database with the following SQL:

select d.data, d.id, d.mt_version, customer_id.data, customer_id.id, customer_id.mt_version 
from public.mt_doc_order as d 
INNER JOIN public.mt_doc_customer as customer_id ON d.customer_id = customer_id.id 
where d.customer_id = 4001

To learn more about the document database features of Marten check out my Pluralsight courses: Getting Started with .NET Document Databases Using Marten and Working with Data and Schemas in Marten.