Durable Azure Functions and Integration Tests - azure

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.

Related

Access FunctionAppDirectory in .NET 5 Azure Function

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;
}
}
}

Error : Format of the initialization string does not conform to specification starting at index 0. when trying to invoke the function app

I am testing my deployed Azure function and getting the following error. My function runs locally connecting to Azure database but fails when its deployed and run. I have configured the application settings to read the secret url to the connection string.
This is how my connectionstring looks like
Server=tcp:ranjitazuredb.database.windows.net,1433;Initial Catalog=Srl;Persist Security Info=False;User ID=usr;Password=pwd;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;
Application setting - Url to the secret
https://srlcustomermanagervault.vault.azure.net/secrets/ConnectionString
Function
public class GetCustomersOrders
{
private readonly ICustomerOrdersRepository _repo;
private readonly IMapper _mapper;
private readonly TelemetryClient _telemetryClient;
public GetCustomersOrders(ICustomerOrdersRepository repo, IMapper mapper, TelemetryConfiguration configuration)
{
_repo = repo;
_mapper = mapper;
_telemetryClient = new TelemetryClient(configuration);
}
[FunctionName("GetCustomersOrders")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "customer-orders")] HttpRequest req,
ILogger log)
{
this._telemetryClient.TrackTrace("C# HTTP trigger function processed a request.");
var customersOrders = _repo.GetCustomerOrders();
return new OkObjectResult(_mapper.Map<List<CustomerOrdersViewModel>>(customersOrders));
}
}
This is how I have assigned the policy
Function start up
[assembly: FunctionsStartup(typeof(Startup))]
namespace SRL.CustomerOrder
{
internal class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
var connectionString = Environment.GetEnvironmentVariable("ConnectionString");
builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
builder.Services.AddScoped<ISrlContext, CustomerManagerContext>();
builder.Services.AddAutoMapper(typeof(Startup));
builder.Services.AddDbContext<CustomerManagerContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddTransient<ICustomerDetailsRepository, CustomerDetailsRepository>();
builder.Services.AddTransient<ICustomerOrdersRepository, CustomerOrdersRepository>();
builder.Services.AddTransient<IOrderDetailsRepository, OrderDetailsRepository>();
}
}
}
Presuming the connection string worked when you used it directly in the app settings I would check out this link
https://learn.microsoft.com/en-us/azure/app-service/app-service-key-vault-references
So in your example you would use
#Microsoft.KeyVault(SecretUri=https://srlcustomermanagervault.vault.azure.net/secrets/ConnectionString
)
The documentation says you need the version id but you do not, (it is a bug that it works). Azure is working on a release so that it works without a version which should probably be out in preview by now and if not shortly. I have talked with several people and have it working for a client without the version.

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.

ExecutionContext is null to non-function methods via IoC, alternative to ExecutionContext.FunctionAppDirectory

ExecutionContext is available to functon parameters.
However, it is not available to other methods via dependency inject, including Functions' constructor, like below:
public class FunctionClass
{
IOtherClass _otherclass;
public FunctionClass(ExecutionContext context, //excetpion
IOtherClass otherclass) //excetpion
{
_otherclass = IOtherClass otherclass
}
[FunctionName("Car")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]
HttpRequest req, ExecutionContext context)
{
}
}
public class OtherClass:IOtherClass
{
public OtherClass(ExecutionContext context) //excetpion
{}
}
I need access to ExecutionContext.FunctionAppDirectory, but don't want to pass ExecutionContext around, because want to use IoC instead.
Is there an alternative way to get the value of ExecutionContext.FunctionAppDirectory?
VS 2017
Azure Functons 2.x
We can use ExecutionContextOptions to get application folder:
public class FunctionClass
private ExecutionContextOptions context;
public FunctionClass(IOptions<ExecutionContextOptions> executionContext) {
this.context = executionContext.Value;
var path = Path.GetFullPath(Path.Combine(context.AppDirectory, "extra.json"));
}
}
Note:
The above works using VS 2019 and Azure Functions 3.x
See:
Github issue
Related post
Based on current documentation, ExecutionContext is only available in the scope of a request when the function method is being invoked.
[FunctionName("Car")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]
HttpRequest req,
ExecutionContext context //<--
) {
var path = context.FunctionAppDirectory;
//...
}
It wont be available as yet in the constructor for injection when the function class is initialized.

ServiceBusTrigger POCO Deserialization

I would like to see if/how it would be possible to plug into the deserialization process for a parameter that's decorated with the ServiceBusTrigger?
Say I have a function that looks like:
public static void HandleMessage([ServiceBusTrigger("myqueue")] MyCustomType myCustomType) { }
How would I go about taking over the deserialization? I know that there is a notion of an IArgumentBindingProvider and IArgumentBinding but it does not look like ServiceBusTrigger supports these concepts.
I know I can use GetBody<Stream>() and deserialize that way but I'd like to know if I can plug into the ServiceBusTrigger's pipeline. By the looks at the SDK, the ServiceBusTrigger has a hard coded list of IQueueArgumentBindingProviders and so I can't add my own.
If you have a look at the Azure WebJobs SDK Extensions, there is an overview on how to create your own bindings :
Binding Extensions Overview
Otherwise the ServiceBusConfiguration exposes a MessagingProvider property that allows you to intercept the ServiceBusTrigger pipeline:
private static void Main()
{
var sbConfig = new ServiceBusConfiguration()
{
MessagingProvider = // you implemetation of the MessagingProvider class goes here !!!
};
var config = new JobHostConfiguration();
config.UseServiceBus(sbConfig);
new JobHost(config).RunAndBlock();
}
Here is a simple skeleton of a MessagingProvider implementation:
public sealed class MyMessagingProvider : MessagingProvider
{
private readonly ServiceBusConfiguration _config;
public MyMessagingProvider(ServiceBusConfiguration config)
: base(config)
{
_config = config;
}
public override MessageProcessor CreateMessageProcessor(string entityPath)
{
return new MyMessageProcessor(_config.MessageOptions);
}
private class MyMessageProcessor : MessageProcessor
{
public MyMessageProcessor(OnMessageOptions messageOptions)
: base(messageOptions)
{
}
public override Task<bool> BeginProcessingMessageAsync(BrokeredMessage message, CancellationToken cancellationToken)
{
// Intercept the message before the execution of the triggerred function
return base.BeginProcessingMessageAsync(message, cancellationToken);
}
public override Task CompleteProcessingMessageAsync(BrokeredMessage message, FunctionResult result, CancellationToken cancellationToken)
{
// Intercept the message after the execution of the triggerred function and before being completed
return base.CompleteProcessingMessageAsync(message, result, cancellationToken);
}
}
}
So you're main function now looks like that:
private static void Main()
{
var sbConfig = new ServiceBusConfiguration();
sbConfig.MessagingProvider = new MyMessagingProvider(sbConfig);
var config = new JobHostConfiguration();
config.UseServiceBus(sbConfig);
new JobHost(config).RunAndBlock();
}

Resources