An ADR Visual Studio Tool – Part 7 – Adding a Context Menu Item

In my previous post I continued with my little series on writing an extension for Visual Studio by completing the functionality to view existing ADRs within the solution. You can see the first post here

In this post, we’ll cover the process of adding a command to the solution and project context menu. Having completed the screen that will show existing ADRs, we now want to allow the user to right-click on a project or solution and select to add a new ADR:

The first step is to add the command; fortunately, this is a pre-made template, so just select to add a new item (ironically), and pick Command:

This will create you a menu item that will appear under the Tools menu by default, and will display a message box when selected:

There are two things that we need to change about this command: the text (we don’t want it to read “Invoke AddAdrCommand”), and the location (we want it to be available from the right-click context menu of a project). Both of those things are changed in the file AdrPackage.vsct (if your project is called Aardvark, this will be named AardvarkPackage.vsct).

If you have a look in that file, you’ll see something called MyMenuGroup; which is referenced in three places. The first defines what it is:

This is where you can change the command text (as I have above).

The second, where it is:

This initially looks like this:

<Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_TOOLS" />

Which adds the menu to the top level Tools menu; change it to:

<Parent guid="guidSHLMainMenu" id="IDM_VS_CSCD_PROJECT_ADD" />

As I have.

There are other options here.

The third is the ID Symbol:

If you decide to change the name of MyMenuGroup, you will need to do so in all three places above.

Getting the Context of the Current Project

Now that we’ve moved the menu to the context menu, we’ll need to find which project we’re in by accessing the DTE Service – this is retrieved by calling the command:

            var dte = await package.GetServiceAsync(typeof(DTE)).ConfigureAwait(false) as DTE2;

You can then find out what’s selected by accessing the selected hierarchy:

            UIHierarchy uih = (UIHierarchy)dte.Windows.Item(
                EnvDTE.Constants.vsWindowKindSolutionExplorer).Object;
            Array selectedItems = (Array)uih.SelectedItems;

Here’s the full code of the Execute method, to display the selected project:

        private async void Execute(object sender, EventArgs e)
        {
            await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken);

            var dte = await package.GetServiceAsync(typeof(DTE)).ConfigureAwait(false) as DTE2;

            UIHierarchy uih = (UIHierarchy)dte.Windows.Item(
                EnvDTE.Constants.vsWindowKindSolutionExplorer).Object;
            Array selectedItems = (Array)uih.SelectedItems;
            foreach (UIHierarchyItem selectedItem in selectedItems)
            {
                // Show a message box to prove we were here
                VsShellUtilities.ShowMessageBox(
                    this.package,
                    selectedItem.Name,
                    "Selected Project",
                    OLEMSGICON.OLEMSGICON_INFO,
                    OLEMSGBUTTON.OLEMSGBUTTON_OK,
                    OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST);
            }
        }

We’re not far off now – there’s a few little issues, but the main thing that’s left is that we’re not actually adding anything – just displaying a message. The next step is to get it to actually add a file, but we’ll come to that in the next post.

The code for this project can be found here.

References

https://docs.microsoft.com/en-us/visualstudio/extensibility/internals/guids-and-ids-of-visual-studio-toolbars?view=vs-2017

https://docs.microsoft.com/en-us/visualstudio/extensibility/internals/guids-and-ids-of-visual-studio-menus?view=vs-2017

https://stackoverflow.com/questions/51967027/vsix-project-context-menu

https://michaelscodingspot.com/visual-studio-2017-extension-development-tutorial-part-3-add-context-menu-get-selected-code/

https://social.msdn.microsoft.com/Forums/sqlserver/en-US/be61c3bb-aac2-48ea-88ad-883a38b526e2/how-to-add-a-command-button-to-the-project-add-submenu-in-a-vsct-file?forum=vsx

https://stackoverflow.com/questions/52489541/problem-with-dte2-for-running-commands-in-visual-studio

https://docs.microsoft.com/en-us/visualstudio/extensibility/walkthrough-accessing-the-dte-object-from-an-editor-extension?view=vs-2019

https://www.mztools.com/articles/2014/MZ2014009.aspx

Unit Testing EF Core – How to Invoke the Contents of OnModelCreating

In this post, I wrote about how you can test an EF Database, using an InMemory database.

I’m guessing a few people reading this will be thinking this is stating the bloody obvious, but it certainly wasn’t to me. Imagine you have a DBContext with some seed data; for example:

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {

            modelBuilder.Entity<EntityType1>().HasData(
                new EntityType1
                {
                    Id = "1",
                    Name = "Test1",
                    Description = "Testing",
                    IsTest = true,
                    SomeNumber = 1
                },

In the above linked article, this seed data will never fire; however, simply calling:

context.Database.EnsureCreated();

Once the context is created, will force the migrations to be run inside the in-memory instance, and you should end up with a system mirroring what you would see, should you run this migration against a physical database.

Caveat Emptor

When using this against EF Core 2.2 I found, what appeared to be, this issue. The error being, while I was trying to insert a record, an error returned saying that an item with the same key exists on the table. However, no such item should exist. The linked article seems to imply that this relates to a bug with the insert for the in-memory database, and that it is resolved for EF Core 3.0. I haven’t validated this, so it may, or may not work to upgrade to EF Core 3.0. Please add a comment if you can validate or negate this.

References

https://github.com/dotnet/efcore/issues/11666

https://github.com/dotnet/efcore/issues/6872

Add HttpClientFactory to an Azure Function

Since I first wrote about dependency injection in Azure Functions things have moved on a bit. These days, the Azure Functions natively* support DI. In this post, I’ll cover, probably the most common, DI scenario: adding HttpClientFactory to your project.

I’ll assume that you have an Azure function, and that it looks something like this:

    public static class Function1
    {
        [FunctionName("Function1")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            string name = req.Query["name"];

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            name = name ?? data?.name;

            return name != null
                ? (ActionResult)new OkObjectResult($"Hello, {name}")
                : new BadRequestObjectResult("Please pass a name on the query string or in the request body");
        }
    }

I’ll assume that, because that’s the default HTTP Trigger function you get when you select to create a new function.

NuGet Packages

You’ll need a few NuGet packages; first, you’ll need:

Install-Package Microsoft.Extensions.Http

Which will allow you to use the HttpClientFactory. You’ll also need some packages for the DI:

Install-Package Microsoft.Azure.Functions.Extensions
Install-Package Microsoft.NET.Sdk.Functions

Startup

If you create a new MVC project, you get a Startup class, which manages all your DI, etc. So we’re going to create one here. Create a Startup.cs class in the function app:

    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services.AddHttpClient();
        }
    }

The Configure method is a member of FunctionsStartup (ctrl-. to add the override). You’ll also need to add the following line outside of the namespace:

[assembly: FunctionsStartup(typeof(FullNamespace.Startup))]

Essentially, FullNamepsace here refers to the fully qualified Startup class in your project. Without this line, nothing will be added to the IoC container.

The AddHttpClient call inside Configure adds HttpClientFactory to your IoC container.

Azure Function

If you have a look at the code above, you’ll notice the class is static. We can’t have constructor injection into a static class (because we can’t have a constructor); let’s change that to an instance class:

        public Function1(IHttpClientFactory httpClientFactory)
        {
            _httpClientFactory = httpClientFactory;
        }

You’ll also need to change the function itself from static to instance:

        public async Task<IActionResult> Run(

That’s it – you can now reference HttpClientFactory from inside your function.

Notes

* Maybe not exactly ‘natively’

References

https://docs.microsoft.com/en-us/azure/azure-functions/functions-dotnet-dependency-injection

React Tips: 5 – Raising Events

Raising an Event is an interesting concept is React: essentially, you pass a function into a component; and then, when the component handles an event, it simply calls the function handler. This is referred to as “Raising” the event, because despite the event happening in the component, it is dealt with by a parent of that component. In C#, you would refer to this as a delegate, and in C, this would be passing a pointer to a function.

Here’s a component:

function MyComponent(props) {
    
    return (
        <div>
            <label>Change the text below</label>
            <input type='text' onChange={props.onChange} />
        </div>
    );
}
export default MyComponent;

If you look at that code, you’ll see that the onChange event is handled by a function passed in, called onChange. Here’s the calling component:

export class Home extends Component {

  render () {
    return (
      <div>
        <MyComponent onChange={this.onChange} />
      </div>
    }

The onChange function in Home will be called whenever the MyComponent textbox is changed.

Important Note

Since this is React, one of the things that you may do in the onChange function is to update the state. I have previously written on how to deal with this, or indeed any method which uses the this property.

Add Entity Framework Core to an Existing Asp.Net Core Project

This is one of those posts born of the fact that I’m constantly googling this, and coming up with videos and Microsoft docs – I just want a single, quick step by step guide; so here it is.

Step One – NuGet packages

If you’re using SQL Server, then you’ll need these packages (technically, you only need the first, unless you want to actually create or run a migration yourself):

Install-Package Microsoft.EntityFrameworkCore.SqlServer
Install-Package Microsoft.EntityFrameworkCore.Tools

Step Two – Create DBContext (and entity classes if needed)

The minimum DBContext looks like this:

    public class MyDbContext : DbContext
    {

        public MyDbContext(DbContextOptions<MyDbContext> options)
            : base(options)
        {

        }

        public DbSet<MyEntity> MyEntities { get; set; }
        public DbSet<MyOtherEntity> MyOtherEntities { get; set; }
    }

You can feed your model directly into EF. That’s much easier to do than it used to be, for example, any field called Id will automatically be inferred to be a Primary Key; however, at some point, you’re going to want to do something data specific to your model, and at that point, you lose the separation between data and business layers. A nice way that I’ve found around this is to subclass your models:

    public class MyEntity : MyModel
    {
    }

Step Three – Startup.cs

You’ll need to register the DbContext in your DI pipeline:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.AddDbContext<MyDbContext>(a =>
                a.UseSqlServer(Configuration.GetConnectionString("SqlConnectionString")));
        }

Your connection string goes in appsettings.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "SqlConnectionString": "Server=(localdb)\\mssqllocaldb;Database=MyDatabase;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

Step Four – Migration

Since you’ve installed EF Tools, you can add a migration:

Add-Migration InitialMigration -StartupProject MySolution.MyProject

The other option is to switch on automatic migrations; although, I would personally advise against that, as you can quickly lose track of what it’s doing.

That’s it – once you run:

Update-Database

You should have a working EF instance.

Step Five (optional) – Auto-Migration

You may also wish to have the system upgrade itself to the latest version of the DB. Whether you choose to do this depends on exactly how much control you need over the changes to your DB. I’ve heard that some people create the migrations as SQL Scripts and hand them to a DBA.

Should you wish to have EF manage that for you, then you can do the following:

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            UpdateDatabase(app);

. . .

private void UpdateDatabase(IApplicationBuilder app)
        {
            using var serviceScope = app.ApplicationServices
                .GetRequiredService<IServiceScopeFactory>()
                .CreateScope();

            using var context = serviceScope.ServiceProvider.GetService<CourseSelectDbContext>();

            context.Database.Migrate();            
        }

That’s it – now, next time I want to find this, I can search my own website!

References

https://docs.microsoft.com/en-us/ef/core/get-started/install/

https://docs.microsoft.com/en-us/aspnet/core/data/ef-rp/intro?view=aspnetcore-3.1&tabs=visual-studio

https://blog.rsuter.com/automatically-migrate-your-entity-framework-core-managed-database-on-asp-net-core-application-start/

An ADR Visual Studio Tool – Part 6 – Visually Opening Files and Changing the Command Title

Following on from this post, which is the latest in a series on creating a Visual Studio Extension, I’m covering a couple of loose ends.

Visually Opening Files

In order to cause Visual Studio to open a file, you’ll need to call .Open on the ProjectItem; this returns a Window; if you then want this window to become visible, simply tell it so. All of this must be done on the UI thread:

            await Microsoft.VisualStudio.Shell.ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
            var window = originalProjectItem.Open();
            window.Visible = true;

Change the Command Title

When you create an extension, by default it gets the same name as the project with the word ‘Window’ appended to it; in my case, that was AdrWindow. In order to change that, have a look for the file:

[ProjectName]Package.vsct

In my case, that was:

AdrPackage.vsct

For the default set-up, the button text will he here:

        <Strings>
          <ButtonText>Adr Manager</ButtonText>
        </Strings>

References

https://stackoverflow.com/questions/26574839/developing-a-visual-studio-extension-how-to-open-a-source-file-and-jump-to-a-sp

My object won’t deserialise using System.Text.Json

I imagine this is a very common scenario, so I thought it worth documenting.

Let’s take the following controller method in a bog standard Asp.Net Core API:

        public async Task<DataContainer> Get()
        {
            var data = Enumerable.Range(1, 5).Select(index => new MyData
            {
                Value1 = index,
                Value2 = "some other value"
            })
            .ToList();

            return new DataContainer()
            {
                Data = data
            };
        }

Here’s a method that consumes it:

            var client = _clientFactory.CreateClient();
            var result = await client.GetAsync($"https://localhost:22222/endpoint");

            using var responseStream = await result.Content.ReadAsStreamAsync();
            var data = await JsonSerializer.DeserializeAsync<DataContainer>(responseStream);

So, you’ve expect this to work, right? It makes no difference what DataContainer looks like, because you’re serialising and deserialising using the same technology. In fact, it just returns a correctly structured object, but every value will be null.

I’ll tell you what does work, is this:

            var client = _clientFactory.CreateClient();
            var result = await client.GetAsync($"https://localhost:22222/endpoint");

            var responseString = await result.Content.ReadAsStringAsync();
            var data = Newtonsoft.Json.JsonConvert.DeserializeObject<DataContainer>(responseString);             

If you look at the JSON, it looks kosher enough (again, the exact structure is irrelevant). So, what’s the issue?

Interestingly, when the JSON is serialised, it is serialised into camelCase; so if you have a variable such as MyData, it will get serialised as myData. Newtonsoft deals with this, because it’s a common use case in .Net; however, System.Text.Json.Deserialize* assumes that the casing will match the object!

This is odd considering it changes it on the way out!!

Okay, so what’s the fix?

You just tell Deserialize* to use camelCase:

            var client = _clientFactory.CreateClient();
            var result = await client.GetAsync($"https://localhost:22222/endpoint");

            using var responseStream = await result.Content.ReadAsStreamAsync();

            var options = new JsonSerializerOptions()
            {
                PropertyNamingPolicy = JsonNamingPolicy.CamelCase
            };
            var data = await JsonSerializer.DeserializeAsync<DataContainer>(responseStream, options);

Annoying, eh?

References

https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to#table-of-differences-between-newtonsoftjson-and-systemtextjson

https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-how-to#use-camel-case-for-all-json-property-names

Using HttpClientFactory

In .Net Framework (prior to .Net Core), HttpClient was something of a pain. You essentially kept it around in a static variable. If you didn’t, and used too many of them, you could end up issues such as socket exhaustion.

I think most people got around this by implementing their own version of a HttpClientFactory… but now you don’t need to.

In this post, I’m covering the simplest possible use case inside an Asp.Net Core application. The articles linked at the bottom of this post go into much more detail.

Usage

The first step to using the HttpClientFactory is to add it to the IoC:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
            services.AddHttpClient();
        }

Then, simply inject it into your controller; for example:

        public HomeController(ILogger<HomeController> logger,
            IHttpClientFactory clientFactory)
        {
            _logger = logger;
            _clientFactory = clientFactory;
        }

Finally, you request a client like this:

var client = _clientFactory.CreateClient();

Then you can use it as a normal HttpClient:

var result = await client.GetAsync($"https://localhost:22222/endpoint");

References

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-3.1

https://www.stevejgordon.co.uk/introduction-to-httpclientfactory-aspnetcore

Calling an Azure Signalr Instance from an Azure function

I’ve been playing around with the Azure Signalr Service. I’m particularly interested in how you can bind to this from an Azure function. Imagine the following scenario:

You’re sat there on your web application, and I press a button on my console application and you suddenly get notified. It’s actually remarkably easy to set-up (although there are definitely a few little things that can trip you up – many thanks to Anthony Chu for his help with some of those!)

If you want to see the code for this, it’s here.

Create an Azure Signalr Service

Let’s start by setting up an Azure Signalr service:

You’ll need to configure a few things:

The pricing tier is your call, but obviously, free is less money than … well, not free! The region should be wherever you plan to deploy your function / app service to, although I won’t actually deploy either of those in this post, and the ServiceMode should be Serverless.

Once you’ve created that, make a note of the connection string (accessed from Keys).

Create a Web App

Follow this post to create a basic web application. You’ll need to change the startup.cs as follows:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSignalR().AddAzureSignalR();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            //app.UseDefaultFiles();
            //app.UseStaticFiles();

            app.UseFileServer();
            app.UseRouting();
            app.UseAuthorization();

            app.UseEndpoints(routes =>
            {
                routes.MapHub<InfoRelay>("/InfoRelay");
            });
        }

Next, we’ll need to change index.html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title></title>
    
    <script src="lib/@microsoft/signalr/dist/browser/signalr.js"></script>
    <script src="getmessages.js" type="text/javascript"></script>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">

</head>
<body>
    <div class="container">
        <div class="row">
            <div class="col-2">
                <h1><span class="label label-default">Message</span></h1>
            </div>
            <div class="col-4">
                <h1><span id="messageInput" class="label label-default"></span></h1>
            </div>
        </div>
        <div class="row">&nbsp;</div>
    </div>
    <div class="row">
        <div class="col-12">
            <hr />
        </div>
    </div>
</body>

</html>

The signalr package that’s referenced is an npm package:

npm install @microsoft/signalr

Next, we need the getmessages.js:

function bindConnectionMessage(connection) {
    var messageCallback = function (name, message) {
        if (!message) return;

        console.log("message received:" + message.Value);

        const msg = document.getElementById("messageInput");
        msg.textContent = message.Value;
    };
    // Create a function that the hub can call to broadcast messages.
    connection.on('broadcastMessage', messageCallback);
    connection.on('echo', messageCallback);
    connection.on('receive', messageCallback);
}

function onConnected(connection) {
    console.log("onConnected called");
}

var connection = new signalR.HubConnectionBuilder()
    .withUrl('/InfoRelay')
    .withAutomaticReconnect()
    .configureLogging(signalR.LogLevel.Debug)
    .build();

bindConnectionMessage(connection);
connection.start()
    .then(function () {
        onConnected(connection);
    })
    .catch(function (error) {
        console.error(error.message);
    });

The automatic reconnect and logging are optional (although at least while you’re writing this, I would strongly recommend the logging).

Functions App

Oddly, this is the simplest of all:

    public static class Function1
    {       
        [FunctionName("messages")]
        public static Task SendMessage(
            [HttpTrigger(AuthorizationLevel.Anonymous, "post")] object message,
            [SignalR(HubName = "InfoRelay")] IAsyncCollector<SignalRMessage> signalRMessages)
        {
            return signalRMessages.AddAsync(
                new SignalRMessage
                {
                    Target = "broadcastMessage",
                    Arguments = new[] { "test", message }
                });
        }
    }

The big thing here is the binding – SignalRMessage binding allows it to return the message to the hub (specified in HubName). Also, pay attention to the Target – this needs to match up the the event that the JS code is listening for (in this case: “broadcastMessage”).

Console App

Finally, we can send the initial message to set the whole chain off – the console app code looks like this:

        static async Task Main(string[] args)
        {
            Console.WriteLine($"Press any key to send a message");
            Console.ReadLine();

            HttpClient client = new HttpClient();
            string url = "http://localhost:7071/api/messages";
            
            HttpContent content = new StringContent("{'Value': 'Hello'}", Encoding.UTF8, "application/json");

            HttpResponseMessage response = await client.PostAsync(url, content);
            string results = await response.Content.ReadAsStringAsync();

            Console.WriteLine($"results: {results}");
            Console.ReadLine();
        }

So, all we’re doing here is invoking the function.

Now when you run this (remember that you’ll need to run all three projects), press enter in the console app, and you should see the “Hello” message pop up on the web app.

References

https://docs.microsoft.com/en-us/aspnet/core/signalr/javascript-client?view=aspnetcore-3.1

https://docs.microsoft.com/en-us/aspnet/core/signalr/dotnet-client?view=aspnetcore-3.1&tabs=visual-studio

https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-signalr-service?tabs=csharp

Add Application Insights to an Azure Resource

Application Insights provides a set of metric tools to analyse the performance and behaviour of various Azure services. For example, you can see how many calls you have to your Azure Web site, or you can see how many errors your service has generated.

This post is concerned with the scenario where you would want to manually log to application insights. The idea being that, in addition to the above metrics, you can output specific log messages in a central location. You might just want to log some debug information (“Code reached here”, “now here” – don’t try and say you’ve never been there!) Or you might find that there is a particular event in your program that you want to track, or maybe you’ve got two different resources, and you’re trying to work out how quick or frequent the communication between them is.

Set-up

The first step is to set-up a new App Insights service in the Azure Portal (you can also use the recently released Azure Portal App).

Select to create a new resource, and pick Application Insights:

When you create the resource, you’ll be asked for some basic details (try to keep the location in the same region as the app(s) you’ll be monitoring):

The instrumentation key is shown in the overview, and you will need this later:

You should be able to see things like failed requests, response time, etc. However, we’ve just configured this, so it’ll be quiet for now:

Check the “Search” window (which is where log entries will appear):

The other place you can see the output is “Logs (Analytics)”.

Create Web Job

The next thing we need is something to trace; let’s go back to a web job.

Once you’ve set-up your web job, app AppInsights from NuGet:

Install-Package ApplicationInsights.Helpers.WebJobs

The class that we’re principally interested here is the TelemetryClient. You’ll need to instantiate this class; there’s two ways to do this:

var config = Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration.CreateDefault();
 
var tc = new TelemetryClient(config);

This works if you link the App Insights to the resource that you’re tracking in Azure; you’ll usually to that here:

Once you’ve switched it on, you can link your resource; for example:

The other way to link them, without telling Azure that they are linked, is this:

TelemetryConfiguration.Active.InstrumentationKey = InstrumentationKey;

(You can use the instrumentation key that you noted earlier.)

Tracing

Now you’ve configured the telemetry client, let’s say you want to track an exception:

    var ai = new TelemetryClient(config);
    ai.TrackException(exception, properties);

Or you just want to trace something happening:

    var ai = new TelemetryClient(config);
    ai.TrackTrace(text);

Side note

The following code will result in a warning, telling you that it’s deprecated:

    var ai = new TelemetryClient();
    ai.TrackTrace(text);

References

http://pmichaels.net/2017/08/13/creating-basic-azure-web-job/

https://blogs.msdn.microsoft.com/devops/2015/01/07/application-insights-support-for-multiple-environments-stamps-and-app-versions/

https://docs.microsoft.com/en-us/azure/application-insights/app-insights-cloudservices

https://docs.microsoft.com/en-us/azure/application-insights/app-insights-windows-desktop

https://github.com/MicrosoftDocs/azure-docs/issues/40482