Access FunctionAppDirectory in .NET 5 Azure Function - azure

I need to have access to FunctionAppDirectory in Azure Function
Here is a simplified version of the function
[Function("Test")]
public static HttpResponseData Test([HttpTrigger(AuthorizationLevel.Function, "post", Route = "Test")] HttpRequestData req,
ExecutionContext context, FunctionContext fContext)
{
var log = fContext.GetLogger(nameof(TestOperations));
log.LogInformation(context?.FunctionAppDirectory?.ToString());
return req.CreateResponse(HttpStatusCode.OK);
}
ExecutionContext here is null.
My Program.cs file
class Program
{
static Task Main(string[] args)
{
var host = new HostBuilder()
.ConfigureAppConfiguration(configurationBuilder =>
{
configurationBuilder.AddCommandLine(args);
})
.ConfigureFunctionsWorkerDefaults()
.ConfigureServices(services =>
{
// Add Logging
services.AddLogging();
})
.Build();
return host.RunAsync();
}
}
Azure Function running in .NET 5
How I can configure binding for ExecutionContext or get FunctionAppDirectory in other ways?

As Alex mentioned in the comment, azure function .net 5 is not support 'context.FunctionAppDirectory' to get the directory of function app now.
In function app 3.0, the 'ExecutionContext' is be designed in the package 'Microsoft.Azure.WebJobs'. But in your code, the ExecutionContext is from 'System.Threading'. So these are different classes.
You can use below code to get the azure function directory in .NET 5.0 azure function:
using System;
using System.Collections.Generic;
using System.Net;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;
namespace FunctionApp6
{
public static class Function1
{
[Function("Function1")]
public static HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
FunctionContext executionContext)
{
var logger = executionContext.GetLogger("Function1");
logger.LogInformation("C# HTTP trigger function processed a request.");
var response = req.CreateResponse(HttpStatusCode.OK);
response.Headers.Add("Content-Type", "text/plain; charset=utf-8");
var local_root = Environment.GetEnvironmentVariable("AzureWebJobsScriptRoot");
var azure_root = $"{Environment.GetEnvironmentVariable("HOME")}/site/wwwroot";
var actual_root = local_root ?? azure_root;
response.WriteString(actual_root);
return response;
}
}
}

Related

OpenTelemetry & correlation support for Azure Functions

I have a project written in .net 6. I use Azure Function to send data to Service bus and MediatR. How can I add Open Telemetry to my Azure functions?
[Function("EBcktErroredFunction")]
public Task EBcktErroredFunctionAsync([ServiceBusTrigger("e-bckt-errored", "bckts-creation-finalize")] EBcktErrored payload)
{
return _mediator.Send(
new SyncBcktErroredToStoreCommand
{
SBcktId = payload.BcktId,
EBcktId = payload.ExternalBcktId
}
);
Not related but in the bicep file it looks like this;
resource eBcktErroredSubscription 'Microsoft.ServiceBus/namespaces/topics/subscriptions' = {
name: '${serviceBusName}/e-bckt-errored/${appName}'
properties: {
autoDeleteOnIdle: ''
deadLetteringOnMessageExpiration:
defaultMessageTimeToLive: ''
enableBatchedOperations: true
lockDuration: ''
}
}
You can find a full example on GitHub and a corresponding discussion about "Using OpenTelemetry Sdk with Azure Functions".
Most relevant code (you should look at the full code for reference):
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
// OpenTelemetry Resource to be associated with logs, metrics and traces
var openTelemetryResourceBuilder = ResourceBuilder.CreateDefault().AddService("opentelemetry-service");
// Enable Logging with OpenTelemetry
builder.Services.AddLogging( (loggingBuilder) =>
{
// Only Warning or above will be sent to Opentelemetry
loggingBuilder.AddFilter<OpenTelemetryLoggerProvider>("*", LogLevel.Warning);
}
);
builder.Services.AddSingleton<ILoggerProvider, OpenTelemetryLoggerProvider>();
builder.Services.Configure<OpenTelemetryLoggerOptions>( (openTelemetryLoggerOptions) =>
{
openTelemetryLoggerOptions.SetResourceBuilder(openTelemetryResourceBuilder);
openTelemetryLoggerOptions.IncludeFormattedMessage = true;
openTelemetryLoggerOptions.AddConsoleExporter();
}
);
// Enable Tracing with OpenTelemetry
var openTelemetryTracerProvider = Sdk.CreateTracerProviderBuilder()
.SetResourceBuilder(openTelemetryResourceBuilder)
.SetSampler(new AlwaysOnSampler())
.AddAspNetCoreInstrumentation()
.AddConsoleExporter()
.Build();
builder.Services.AddSingleton(openTelemetryTracerProvider);
// Enable Metrics with OpenTelemetry
var openTelemetryMeterProvider = Sdk.CreateMeterProviderBuilder()
.SetResourceBuilder(openTelemetryResourceBuilder)
.AddAspNetCoreInstrumentation()
.AddMeter(Function1.MyMeter.Name)
.AddConsoleExporter(consoleOptions =>
{
consoleOptions.MetricReaderType = MetricReaderType.Periodic;
consoleOptions.AggregationTemporality = AggregationTemporality.Cumulative;
consoleOptions.PeriodicExportingMetricReaderOptions.ExportIntervalMilliseconds = 10000;
})
.Build();
builder.Services.AddSingleton(openTelemetryMeterProvider);
[FunctionName("Function1")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogWarning("C# HTTP trigger function processed a request.");
MyCounter.Add(1, new("name", "apple"), new("color", "red"));
string name = req.Query["name"];
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
string responseMessage = string.IsNullOrEmpty(name)
? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
: $"Hello, {name}. This HTTP triggered function executed successfully.";
log.LogWarning("Name is {name}", name);
Activity.Current?.SetTag("name", name);
return new OkObjectResult(responseMessage);
}

HTTP Listener in a HTTP Trigger Azure Function

I have a HTTP Listener console app that works on my local machine. When I try to use it inside a HTTP Trigger Azure Function. I always get the 418 error code.
In my console app:
HttpListener listener = new HttpListener();
try
{
listener.Prefixes.Add("http://localhost:11000/");
listener.Start();
} catch (Exception e)
{ // }
do {
var ctx = listener.GetContext();
var res = ctx.Response;
var req = ctx.Request;
var reqUrl = req.Url;
var readStream = new StreamReader(req.InputStream);
var content = readStream.ReadToEnd();
Console.WriteLine(content);
// business logic
readStream.Close();
res.StatusCode = (int)HttpStatusCode.OK;
res.ContentType = "text/plain";
res.OutputStream.Write(new byte[] { }, 0, 0);
res.Close();
if (stopListener) { listener.Stop(); }
} while (listener.IsListening);
Now HTTP Trigger Function uses the HttpRequest class and that seems to give me the 418 error code. I replaced it with HttpListener() but when I add the prefix of the Azure Function string connection (on the CLI), the stream never goes through and its as if its not capturing it? Or what connection should I use? I feel like self-referencing it is the reason its not working.
Azure Function:
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpListener listener,
ILogger log,
IBinder binder)
{//same as above}
Is this the right approach to getting data from an external app? So far this has been the way I can see it working via the HTTP Listener.
Any suggestions are welcomed.
Is this the right approach to getting data from an external app?
The right way to access Data from an external source and any other source. You can create an API and use this API to access data from external sources.
For create azure function click hereby Microsoft documents.
Below sample code for access web API in azure function.
var _httpclient = new HttpClient();
var _response = await _httpclient .GetAsync(rul);
var result_= await _response .Content.ReadAsStringAsync();
its use is just like using API in C# code.
Azure Function Code:-
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.Net.Http;
namespace _73093902_FunctionApp10
{
public static class Function1
{
[FunctionName("Function1")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
var _httpclient = new HttpClient();
var _response = await _httpclient.GetAsync("https://localhost:7101/WeatherForecast");
var result_ = await _response.Content.ReadAsStringAsync();
return new OkObjectResult(result_);
}
}
}
Debug Output:-

Azure Durable Orchestrator + Sub orchestrator task is never resolved

I am trying to build a simple orchestrator func + additional call with sub-orchestrator.
The problem is in main orchestrator and call to await ctx.CallSubOrchestratorAsync("sub-orchestration", someInput) - the task is never resolved/completed... If I change the code in sub orchestrator from await Send_SomeData(); to Send_SomeData().Wait(); then it works, but that is not what I want. As far as I know async/await is supported in orchestration funcs
Thank you!
The code is below:
Main Orchestrator
[FunctionName("main-orchestration")]
public static async Task MainOrchestrationAsync(
[OrchestrationTrigger] IDurableOrchestrationContext ctx,
ILogger log)
{
var someInput = "my msg";
await ctx.CallSubOrchestratorAsync("sub-orchestration", someInput);
log.LogWarning("finish");
}
Sub Orchestrator
[FunctionName("sub-orchestration")]
public static async Task SubOrchestrationAsync(
[OrchestrationTrigger] IDurableOrchestrationContext ctx,
ILogger log)
{
log.LogWarning("Sub Start");
await Send_SomeData(some_url, some_message);
log.LogWarning("Sub End");
}
Send_SomeData
public static async Task<string> ConnectAsync(string url, string message)
{
using var request = new HttpRequestMessage
{
RequestUri = new Uri(url),
Method = HttpMethod.Post,
Content = new FormUrlEncodedContent(new[]
{
...message...
})
};
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
UPDATE
I've decided to create an example from scratch, to not miss something and get something that can be copy/paste for debugging.
Below is another example. Main End log message never appeared, so Task from .CallSubOrchestratorAsync never resolved.
What am I missing?
Thanks again!
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.DurableTask;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Extensions.Logging;
namespace _Await_Issue
{
public static class Function1
{
[FunctionName("Main")]
public static async Task RunOrchestrator(
[OrchestrationTrigger] IDurableOrchestrationContext context,
ILogger log)
{
log.LogWarning("Main Start");
try
{
await context.CallSubOrchestratorAsync("Sub", null);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
log.LogWarning("Main End");
}
[FunctionName("Sub")]
public static async Task RunSubOrchestrator(
[OrchestrationTrigger] IDurableOrchestrationContext context,
ILogger log)
{
log.LogWarning("Sub Start");
var data = await GetDataAsync("https://www.google.com");
log.LogWarning("Sub End");
}
[FunctionName("Start")]
public static async Task<HttpResponseMessage> HttpStart(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")]
HttpRequestMessage req,
[DurableClient] IDurableOrchestrationClient starter,
ILogger log)
{
// Function input comes from the request content.
string instanceId = await starter.StartNewAsync("Main", null);
log.LogInformation($"Started orchestration with ID = '{instanceId}'.");
return starter.CreateCheckStatusResponse(req, instanceId);
}
public static async Task<string> GetDataAsync(string url)
{
var httpClient = new HttpClient();
using var request = new HttpRequestMessage
{
RequestUri = new Uri(url),
Method = HttpMethod.Get,
};
var response = await httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
}
I found the light here ))
Answering on my own question as it might help others.
As Peter Bons pointed Send_SomeData method was under suspicion ))
The problem in general is in "Deterministic Apis" as Azure team call in the docs
Deterministic Apis Explanation
In my case Send_SomeData inside uses HttpClient to communication with external endpoint.
It's not recommended as it might result in an unexpected behavior as in my case with await...
If there are such portions of code (e.g. Send_SomeData in my case) put it in Activity and call this with context.CallActivityAsync() - (be aware that Activity funcs has limited time to execute)

Durable Azure Functions and Integration Tests

I'm trying to test (integration tests) my Azure Durable Function v3 with SpecFlow and MSTest.
Functions are initialized with DI and Startup class:
[assembly: FunctionsStartup(typeof(Startup))]
namespace MyNamespace.Functions
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
ConfigureServices(builder.Services);
}
...
Orchestrator entry point is triggered by HTTP endpoint:
public class Function1
{
[FunctionName(nameof(Function1))]
public async Task<IActionResult> DoYourJob(
[HttpTrigger(AuthorizationLevel.Anonymous, methods: "post", Route = "api/routes/function1")] HttpRequestMessage httpRequest,
[DurableClient] IDurableOrchestrationClient starter)
{
...
My IntegrationTest constructor initializes Az Function with HostBuilder (thanks to this article):
[Binding]
public sealed class Function1StepDefinitions
{
private readonly IHost _host;
private readonly Function1 _function;
public Function1StepDefinitions()
{
var startup = new MyNamespace.Functions.Startup();
_host = new HostBuilder()
.ConfigureWebJobs(config =>
{
config.AddDurableTask(options =>
{
options.HubName = "MyTaskHub";
});
startup.Configure(config);
})
.ConfigureServices(ReplaceTestOverrides) // Method to replace/mock some external services
.Build();
_function = new Function1(Host.Services.GetRequiredService<IOptions..., ..);
And in my test I dont know how to retrieve IDurableOrchestrationClient to call my HTTP Trigger:
[When(#"I call my function")]
public async Task WhenICallMyFunction()
{
var request = new HttpRequestMessage();
await _function.DoYourJob(
request,
Host.Services.GetService<IDurableOrchestrationClient>()); // This is always null
...
Is there a way to retrieve this IDurableOrchestrationClient and test my whole Function (with Orchestrator and Activities calls)?
Looks like there is a IDurableClientFactory that you can inject instead based on this code.
Using this, you can create a client to use as shown in this sample.

Application Insights with multiple applications

I have an Application Insights which logs traces from an App Service and an App Function (one resource for 2 functions).
I need to filter traces according to the resource (App Service or App Function) and, if possible, for the App Function which function is actually logging.
Looking at the traces I see the following list of properties:
I thought to find the resource name in the appName property, instead there is the Application Insights resource name, which is useless for me, since all those traces are from that resource.
Note: I don't like the workaround to set a prefix in the message to filter the traces.
UPDATE
I followed Peter Bons suggestions and I created a brand new Function V3 project. The basic version of the project worked also without the Telemetry Initializer, I mean that the Cloud_RoleName property was correctly populated.
Then, I added my changes to adapt the sample code and I found that the problem comes up when I inject a new Telemetry Client. I know, it is not recommended to manually inject TelemetryClient in App Function, but I absolutely need to send Custom Event to Application Insights and, as far as I know, it is not possible with ILogger interface used by default in App Function.
Startup.cs
public class Startup : FunctionsStartup
{
private TelemetryConfiguration telemetryConfiguration;
public override void Configure(IFunctionsHostBuilder builder)
{
var localRoot = Environment.GetEnvironmentVariable("AzureWebJobsScriptRoot");
var azureRoot = $"{Environment.GetEnvironmentVariable("HOME")}/site/wwwroot";
var configBuilder = new ConfigurationBuilder()
.SetBasePath(localRoot ?? azureRoot)
.AddEnvironmentVariables()
.AddJsonFile("local.settings.json", optional: true, reloadOnChange: true);
var configuration = configBuilder.Build();
if (builder != null)
{
this.ConfigureServices(builder.Services, configuration);
}
}
private void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
services.AddSingleton<ITelemetryInitializer>(x => new CustomTelemetryInitializer(configuration["appFunctionName"]));
telemetryConfiguration = new TelemetryConfiguration(configuration["APPINSIGHTS_INSTRUMENTATIONKEY"]);
telemetryConfiguration.TelemetryInitializers.Add(new OperationCorrelationTelemetryInitializer());
var telemetryClient = new TelemetryClient(telemetryConfiguration);
services.AddSingleton(telemetryClient);
services.AddSingleton<ISampleInterface, SampleService>();
}
}
CustomTelemetryInitializer.cs
public class CustomTelemetryInitializer : ITelemetryInitializer
{
private readonly string roleName;
public CustomTelemetryInitializer(string roleName)
{
this.roleName = roleName;
}
public void Initialize(ITelemetry telemetry)
{
if (string.IsNullOrEmpty(telemetry?.Context?.Cloud?.RoleName))
{
telemetry.Context.Cloud.RoleName = roleName;
}
}
}
SampleService.cs
public class SampleService : ISampleInterface
{
private TelemetryClient telemetryClient;
public SampleService(TelemetryClient telemetryClient)
{
this.telemetryClient = telemetryClient;
}
public void TestAppInsights()
{
telemetryClient.TrackEvent("Sample Custom Event with init");
telemetryClient.TrackTrace("Sample Custom Trace with init");
}
}
Function.cs
public class Function1
{
private ISampleInterface service;
public Function1(ISampleInterface service)
{
this.service = service;
}
[FunctionName("Function1")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request with init.");
this.service.TestAppInsights();
string name = req.Query["name"];
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
string responseMessage = string.IsNullOrEmpty(name)
? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
: $"Hello, {name}. This HTTP triggered function executed successfully.";
return new OkObjectResult(responseMessage);
}
}
How about inspecting the cloud_RoleName property, available to all telemetry? By default it will have the name of the webapp or function (including slot names) as the value.
Otherwise, if you want to add custom properties or modify properties for all telemetry at one place you can make use of a telemetry initializer as demonstrated here:
using Microsoft.ApplicationInsights.Channel;
using Microsoft.ApplicationInsights.Extensibility;
namespace CustomInitializer.Telemetry
{
public class MyTelemetryInitializer : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
telemetry.Context.Cloud.RoleName = "HttpTriggered";
}
}
}
This avoids having to prefix all traces as you mentioned as a work around by having a single piece of code all telemetry passes through:
Another thing
[...] but I absolutely need to send Custom Event to Application Insights and, as far as I know, it is not possible with ILogger interface used by default in App Function.
Do note that you can redirect the output emitted by using the ILogger interface to Application Insights. It will show up as a trace.

Resources