Tip #1145: Tracing in Azure Functions MkIII

This never ends. Shortly after I finished writing about tracing in Azure Functions, I found Daryl “Always Raising” LaBar explaining how to use ExtendedOrganizationService wrapper to easily capture everything in your plugin:

The primary purpose of the tracing wrapper in Azure Functions is to reuse existing functionality you might already have implemented in a separate assemblies. If the existing code uses Trace then my wrapper works nicely redirecting the output to ILogger supplied by the function. But Daryl’s point is that the existing code, if it comes from plugins / workflows would have used ITracingService provided by the execution context. So we need our logger to understand ITracingService as well. Fear not, let me raise the LaBar by standing on his shoulders. We’ll take an extra parameter and implement the interface:

using Microsoft.Extensions.Logging;
using Microsoft.Xrm.Sdk;
using System.Diagnostics;

namespace YourNamespace
{
  // Log writter for working with Azure Functions 
  public class TraceWriterListener : 
               TraceListener, ITracingService
  {
    private ILogger _log;
    private ITracingService _tracer;

    public TraceWriterListener(string name, 
          ILogger logger,
          ITracingService tracer = null) : base(name)
    {
      _log = logger;
      _tracer = tracer;
    }

    public TraceWriterListener(ILogger logger,
          ITracingService tracer = null) : base()
    {
      _log = logger;
      _tracer = tracer;
    }

    // this is the one we have to overwrite to get 
    // the logging level right
    public override void TraceEvent(
          TraceEventCache eventCache, 
          string source, TraceEventType eventType, 
          int id, string message)
    {
      switch(eventType)
      {
        case TraceEventType.Verbose: 
          _log?.LogDebug(message); 
          break;
        case TraceEventType.Information: 
          _log?.LogInformation(message);
          break;
        case TraceEventType.Warning: 
          _log?.LogWarning(message); 
          break;
        case TraceEventType.Error: 
          _log?.LogError(message); 
          break;
        case TraceEventType.Critical: 
          _log?.LogCritical(message); 
          break;
        default:break;
      }
    }

    public override void Write(string message)
    {
      _log?.LogTrace(message);
      _trace?.Trace(message);
    }

    public override void WriteLine(string message)
    {
      _log?.LogTrace(message);
      _trace?.Trace(message);
    }

    // ITracingService implementation
    public void Trace(string fmt, params object[] args)
    {
      _log?.LogTrace(ftm, args);
      _trace?.Trace(fmt, args);
    }
  }
}

Now, you can call your existing AwesomeMethod and pass the wrapper in. That way it will get logged as part of the Functions logging as well as go into the trace output of the Dynamics infrastructure.

public static void Run(
  [TimerTrigger("0 0 0 1 1 *")]TimerInfo myTimer, 
  ILogger log)
{
   // get your tracing service here 
   var tracingService = ...;
   var logger = new TraceWriterListener(
      log, tracingService);
    
   Trace.Listeners.Add(logger);
   Trace.TraceInformation($"Information");
   Trace.TraceWarning($"Warning");
   Trace.TraceError($"Error");
   Trace.WriteLine($"Level comes from ILogger");

   YourSuperDuperExistingClass
     .AwesomeMethod(whatever, logger);
}

(Facebook and Twitter cover photo by Caterina Beleffi on Unsplash)

Tip #1144: How to add business days

Over six months ago I got a tip from Sergio “Behind The Wall” Macías, part of the the driving force behind Spanish-speaking Dynamics 365 community, on how to create a custom workflow activity that adds business days to a specific date in Dynamics 365. In the essence, the calculations are something like this:

while(businessDaysToAdd > 0)
{
  starDate = starDate.AddDays(1);
  if(IsBusinessDay(startDate))
   businessDaysToAdd--;
}

The heart of calculations is, of course, the IsBusinessDay method that would skip obvious Saturdays and Sundays and then go through a holiday calendar to work out if the employees eat turkey instead of helping customers or if it’s business as usual (e.g. help customers eat turkey). (“Go through a holiday calendar” sounds much easier than it actually, is, trust me). I filed the tip with the intention to come back to it until the opportunity presents itself, i.e. until I have a chance to test-drive it using customer’s money, which happened just recently. Because of the vast Dynamics 365 community resources, regardless what the challenge is, I rarely feel the need to either create custom code from scratch or repurpose the existing code. On this occasion, I found two independent open source workflow libraries that would solve my challenge.

Andrii’s Ultimate Workflow Toolkit and Jason’s CRM DateTime Workflow Utilities both have Add Business Days methods though the approach and the parameters are slightly different. Both take existing date and number of days to add as an input, and have the result as an output but that’s where the similarities end.

CRM DateTime Workflow Utilities

The AddBusinessDays activity takes Holiday/Closure Calendar parameter that you can point to a holiday calendar specific for your organization / department / business unit. This is the same calendar that can be used in service scheduling to define availability of the resources. The only downside of the activity implementation is hard-coded Saturdays and Sundays as non-business days. It works in 99% of the cases but on odd occasion business needs to include some of the weekends.

Ultimate Workflow Toolkit

The AddBusinessDays activity in this toolkit does not use holiday calendar but instead takes extra two parameters: weekend days (as a string of numbers from 0 to 6 (Sun to Mon) delimited by ‘|’) and a Fetchxml expression that would return days to skip (holidays). This “developer-friendly” approach works well if your organization does not use calendars but instead keeps holidays in a custom entity.

Waiting

If you adding business days only with the purpose of waiting until the certain number of days passes e.g. remind customer of an unpaid invoice after 5 business days then you might want to consider codeless approach using SLAs.

Thanks to Andrii “Granny’s Moonshine” Butenko and Jason “I can make a kettle talk to CRM” Lattimer for their awesome contributions – I use their toolkits on a daily basis.

(Facebook and Twitter cover photo by Curtis MacNewton on Unsplash)

Tip #1143: Managing Dynamics Goes Beyond Technology

It is easy to focus on training and certifications and think this will be enough to implement or maintain a successful Dynamics system but, especially with process management systems like Dynamics, being tech savvy is not enough.

Certainly knowing the system, its limitations and capabilities is important but understanding the impact of change is vital for success. The traditional paradigm is People, Process, and Technology with a change in one having an impact on the other.

To successfully administer or implement Dynamics, here are some of the skills and talents you will need access to:

People

  • Change communication: The ability to articulate what is coming, why it is beneficial, and to get buy-in
  • Capable trainers/technical writers: The ability to empower the users to embrace the change
  • Effective feedback mechanisms: Telling is one thing but listening is vital

Process

  • Process discovery: Find out how things are done and how they will be impacted through change
  • Process modelling: Documenting process, an often overlooked task, provides the opportunity to reflect and review where the biggest improvements can be made
  • Process performance management: Establishing measures for what defines a successful process provides proof of the benefits Dynamics is bringing

Technology

  • Functional knowledge: No point reinventing the wheel when it comes standard. Knowing what Dynamics provides means knowing how to use it to the best advantage
  • Technical knowledge: Where Dynamics ends, development begins. Knowing how to extend Dynamics takes a good system and makes it great
  • Roadmap knowledge: Knowing the future of Dynamics means developing the system to work with Microsoft and their vision for the product, not against it. Significant effort can be saved with a little hindsight.

Tip #1142: Tracing in Azure Functions MkII

When describing tracing  in Azure Functions previously, I dropped almost in passing that to capture .NET traces in Azure Functions is easy – just create your own TraceListener. I also added that

the code takes a shortcut with log.Info and requires a bit of tuning like mapping logging levels from Connector to TraceWriter but those are the details we can live without

On one of the existing projects, that extensively uses Trace, I had an opportunity to eat my own words. Turns out, we cannot live without those pesky details because logging level will be totally useless in that implementation.

Getting levels under control turned out to be a small challenge because, technically speaking, listener is just that and shouldn’t concern itself with the logging level – you do what you’re told, basically. After some table bending up-and-down head movements, I managed to come up with TraceWriterListener class that takes Azure Functions log interface as a parameter and correctly intercepts all levels of tracing, emitting the appropriate statements. Here is the class in all its glory:

using Microsoft.Extensions.Logging;
using System.Diagnostics;

namespace YourNamespace
{
  // Log writter for working with Azure Functions 
  public class TraceWriterListener : TraceListener
  {
    private ILogger _log;

    public TraceWriterListener(string name, 
          ILogger logger) : base(name)
    {
      _log = logger;
    }

    public TraceWriterListener(ILogger logger) : base()
    {
      _log = logger;
    }

    // this is the one we have to overwrite to get 
    // the logging level right
    public override void TraceEvent(
          TraceEventCache eventCache, 
          string source, TraceEventType eventType, 
          int id, string message)
    {
      switch(eventType)
      {
        case TraceEventType.Verbose: 
          _log?.LogDebug(message); 
          break;
        case TraceEventType.Information: 
          _log?.LogInformation(message);
          break;
        case TraceEventType.Warning: 
          _log?.LogWarning(message); 
          break;
        case TraceEventType.Error: 
          _log?.LogError(message); 
          break;
        case TraceEventType.Critical: 
          _log?.LogCritical(message); 
          break;
        default:break;
      }
    }

    public override void Write(string message)
    {
      _log?.LogTrace(message);
    }

    public override void WriteLine(string message)
    {
      _log?.LogTrace(message);
    }
  }
}

If you add this class to your function project (or drop as csx into your function environment if you are not using projects), then the function code can use something like:

public static void Run(
  [TimerTrigger("0 0 0 1 1 *")]TimerInfo myTimer, 
  ILogger log)
{
   var logger = new TraceWriterListener(log);
   Trace.Listeners.Add(logger);
   Trace.TraceInformation($"Information");
   Trace.TraceWarning($"Warning");
   Trace.TraceError($"Error");
   Trace.WriteLine($"Level comes from ILogger");
}

which makes migration of the existing code fairly easy.

Bonus

You probably noticed the use of ILogger interface where previously TraceWriter was sitting. As of recently quite some time ago, functions now have support for logging through the Microsoft.Extensions.Logging.ILogger interface. As per documentation: at a high level, this is not much different than logging via the TraceWriter: logs continue to go to the file system and will also go to Application Insights (currently in Preview) if the APPINSIGHTS_INSTRUMENTATIONKEY app setting is set. The main advantage of using ILogger is that you get support for structured logging via Application Insights, which allows for richer Analytics support. To use ILogger as your logging interface, simply add a parameter to your function signature and use any of the logger extensions.

Tip #1140: Get ready for October 2018 release

No, Microsoft didn’t invent the time machine (though you can buy one in store). As part of modernizing the way Dynamics 365 gets updated, the team has just released October 2018 release notes.

Why did the notes get released 3 (and for some features 5) months ahead of schedule? So that customers and partners can start planning for the new and exciting capabilities coming to Dynamics 365. This is also a great opportunity to provide early feedback (by emailing releasenotes@microsoft.com) that will be reviewed and incorporated, if applicable and makes sense.

Overall, the document is a mixed bag. It’s understandable because the contributions were coming from the independent teams. Still, it’s hard to place features like “Improved grid with copy and paste” in Business Central on the same level as Channel Integration Framework.

What caught my attention (YMMV, as I have a developer/platform/ISV bias):

  • Sales: Integration with Microsoft Teams and AI capabilities
  • Service: Service scheduling using URS (Unified Resource Scheduling), Omni-channel Engagement Hub, Channel Integration Framework, and Bring Your Own Bot capabilities
  • Portals: SharePoint integration, embedded Power BI charts, and (finally!) configuration migration schema for portals
  • URS: General enhancements, in-form scheduling, and self-service scheduling APIs
  • PowerApps: embedded canvas apps on entity forms, custom size and responsive layout for canvas apps, ALM and admin enhancements, native support for CDS data types
  • Flow: design Flows in Vision o__O, Flow inception (Flow management connector)
  • Data Integration: new and improved development and consumption for connectors

Commitment to online couldn’t be made clearer with the word “premises” mentioned only in relation to Russian localization of Finance and Operations, Dynamics 365 for Retail, Business Central, and On-premises Data gateway.

Download 239 pages (including 14 pages of TOC) of goodness, get a drink of your choice, and spend a few minutes (or hours) reading it – it will be most certainly worth your time.

(Facebook and Twitter cover photo by Bud Helisson on Unsplash)

Tip #1139: Giving Good Demonstration

Agile projects have ‘ceremonies’ which is code for compulsory meetings with various purposes. One of these ceremonies is the ‘showcase’ where the developers get to show what they have built during the sprint. When you have a new sprint every three weeks, you get used to presenting bits of functionality. Whether it is a fifteen minute showcase presentation or a two hour pre-sales demonstration, there are some things I always do without fail. Here are my tips for Dynamics demos.

Write a Script

If you are behind a keyboard walking through functionality, no one cares if you have a sheet of paper next to the keyboard guiding you. Generally my script has two sections: The Setup and the Running Sheet.

Document the Setup

The setup lists which browsers you are using, what URL you are going to in each tab, the default screen you are starting on and the login (and password hint if required) you are using. While the security purists will frown at the idea of a password hint, the last thing you want to be doing is guessing a password in front of a crowd.

Document the Running Sheet

This is what you will be showing. I generally set it up as a table of two columns: ‘Action’ and ‘Points To Make’. It is very easy to forget the “so what?” factor when showing a software feature so the ‘Points to Make’ column is a great way to have a checklist of points you wish to cover for those audience “aha! moments”.

Run Through the Script

Do this at least once before the real thing. I usually need at least a couple of run throughs,with adjustments, before the demonstration runs smoothly. It might take you 30 minutes to run through and perfect that 15 minute presentation but it is worth it.

Put in Data

Running through the script will quickly show where you need data in the system to support your presentation. Do not compromise an otherwise great show with dodgy data. Get rid of those records named ‘Test’ and use something meaningful to the audience. Again, it may take you three times as long to set up the data as it takes to present but it is worth it.

Do Not Forget to Reset

If you run through your script you will likely be altering the system as you go. Always remember to remove the data from the fields you populated and delete those records you created. Otherwise the audience may see the punchline before you have given them the setup.

Know Your Audience

Know your audience and play to them. I once presented a security background check system in Dynamics to a government department. Thinking it might get a few smiles, my sample contact was the current Prime Minister. It did not go down well. If unsure, use characters from pop culture such as The Simpsons, Harry Potter or Game of Thrones. Seeing a background check done on Jon Snow or Severus Snape will get a wry smile from the fans while the rest will be none the wiser.

Set up Early

Always set up early. This gives you a chance to check internet speeds, connectivity to projectors, required resolution so the audience can see clearly and all the other things you need to do for a smooth presentation. Arriving just on time guarantees the audience will see you fiddling with cables and browsers. Everything should be in place before the first person arrives.

There are many other things you can do to nail your presentation but these are my minimums that I do every time. If you have other must-dos, feel free to leave them in the comments.

Tip #1138: Obtenga your Dynamics 365 trial dans la правильном Sprache

Dynamics 365 supports 43 base languages, and sometimes you need the right one for the trial. In theory, it’s as easy as 1-2-3:

Un

Clément “French but not Tanguy” Olivier was having no luck provisioning a trial instance in his langue maternelle:

For a client demo, I had to create a new Trial instance (on the same tenant he already has) in French and without any “extra” modules, only the Sales one. Using Chrome, when I follow the process from https://trials.dynamics.com the trial is automatically created in English with all modules (including Field services and the others). Using Internet Explorer, I do have the choice of modules installed but also the language, currencies  and name… My laptop is in English does it have an impact here?

Zwei

Guido “Future Olive Farmer” Preite begged to differ:

I just created a new trial with Chrome and the wizard asked me the modules and the currencies. I used the link “Are you signing up on behalf of a customer or using this trial for development purposes? Sign up here.” (Option 2 here).

Ntathu

James Oleinik (the authority on the subject, in case it’s not clear) explains:

If you sign-up for a new trial from trials.dynamics.com and select one of the applications, then we have logic today that automatically select the language for your trial based on your browser locale. If you sign-up for a new trial from the Office admin center (or via the dev flow that Guido outlines), then you are presented an option to manually select the language for the trial.

Tîpp Jäår $0.02

If you need something other than Gẹẹsi then get your browser right, folks, it ain’t that hard.

Languages in a browser

(Social media cover photo by Jon Tyson on Unsplash)

Tip #1137: Learn language and study API to simplify your code

I already wrote about the benefits of learning new language features. Equally important is to understand what assemblies are available as part of Dynamics 365 SDK, how they work, what classes and interfaces are available, and how the edge cases work (basically, “what-if” scenarios, like “what if this is null”).

This week I had “privilege” to sink my teeth into some old code written by a developer with a particularly severe case of spießrutenlaufen. My eyes are still bleeding. Consider this fragment:

DateTime? DateAccepted = null;

if(entity != null) 
{ 
   DateAccepted = (entity.Contains("new_dateacc") 
   && entity["new_dateacc"] != null 
   && entity["new_dateacc"] != string.Empty) ?
   entity.GetValue<DateTime>("new_dateacc") 
    : (DateTime?)null;
}

I did some line wrapping for your viewing pleasure, original code was a one-liner. At this point in time you should be able to hear my eyes rolling all the way to the back of my head. How about this instead?

var DateAccepted = 
 entity?.GetAttributeValue<DateTime?>("new_dateacc");

There is no magic here, I simply used:

If you are unsure how things would work out if, for example, you used GetAttributeValue<DateTime>, why don’t you whip out some quick code to test the behavior:

As you can see, return seems to be default(T) for non-nullable T types. Yep, I’m using the goodness of LINQPad, a must have for every developer.

(Social media cover photo by Easton Oliver on Unsplash)

Tip #1136: How emails are resolved to the Dynamics 365 records

Literally a week later after Jonas “The Shuffler” Rapp was puzzled how From fields are resolved (see tip 1119),  Josh Wells from the cold city of Fargo (where some of the Microsoft folks tend to congregate) chimed in with the final resolution to the problem and a very good advice:

One of the best changes to Microsoft Docs is the fact that you can provide content feedback. You can do it within the article or at GitHub (scroll to the bottom of the article). This is an amazing opportunity for things like this to get changed or added into our documentation without having to go through different layers of providing input. With this said, I’ve gone ahead and filed this feedback within the following GitHub Issue for this article: https://github.com/MicrosoftDocs/dynamics-365-customer-engagement/issues/33

I would definitely encourage all of you to do the same when you see that documentation is lacking or needs further input.

Tîpp Jäår $0.02

The end result is a very succinct authoritative description of how emails are resolved. As we already mentioned, Microsoft Docs are now Open Source. And Josh is spot on: if you see something that you’re not happy about, do something about it.

(Facebook and Twitter cover photo by Clark Tibbs on Unsplash)