Azure Functions - Dynamic blob container in Blob binding - azure

I need to add files to an Azure Storage Account using an Azure Function App with a Queuetrigger. But the container needs to be added dynamically. How is that possible?
public static async Task Run(
[QueueTrigger("activitylogevents", Connection = "StorageConnectionAppSetting")] Log activitylogevents,
Dynamic ==> [Blob("{dynamicc container}/dev/bronze/logs.json", FileAccess.Read)] Stream streamIn,
ILogger log)
{ ... Code doing stuff ... }
Thanks

You can make use of IBinder to dynamically define your BlobAttribute:
public static void MyFunction1(
[QueueTrigger("activitylogevents", Connection = "StorageConnectionAppSetting")] Log activitylogevents,
IBinder binderIn,
ILogger log)
{
var blobInAttribute = new BlobAttribute(myUrl, FileAccess.Read) { Connection = "StorageConnectionAppSetting" };
var streamIn = binderIn.Bind<Stream>(blobInAttribute);
//other code
}

Related

Running some startup tasks in Configure of Azure FunctionsStartup

I have a function that is bound to a QueueTrigger. In this function I generate a file and write this to a Blob Storage.
But before writing (uploading) the file I want to make sure that the container exists. Is the Configure method in the startup class that inherits FunctionsStartup the right place? It feels wrong to do it every time the trigger runs, isn't it?
I'm using DI to supply my function class some services.
[FunctionName("MyFunction")]
public async Task Run([QueueTrigger(MyQueueName, Connection = "AzureWebJobsStorage")]
MyObject queueMessage, ILogger log)
{
var bytes = Encoding.UTF8.GetBytes("MyFileContent");
// Check if container exists - but not everytime?
var blobClient = new BlobClient(_settings.ConnectionString, _settings.ContainerName, _settings.FileName);
await using var memoryStream = new MemoryStream(bytes);
await blobClient.UploadAsync(memoryStream, true);
}
using MyApp.FunctionApp;
using MyApp.FunctionApp.Options;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
[assembly: FunctionsStartup(typeof(Startup))]
namespace MyApp.FunctionApp
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
// Some startup tasks here like ensuring existence of a Blob Container?
builder.Services.AddOptions<Storage>().Configure<IConfiguration>((settings, configuration) =>
{
configuration.GetSection("Storage").Bind(settings);
});
}
}
}
Depending on the frequency of how often you want to check, you could even do something as simple as this:
//shared variable for all instances that run on the same VM
private static bool HaveCheckedBlobContainer = false;
Then, on each invocation:
if (!HaveCheckedBlobContainer)
{
//perform check ...
HaveCheckedBlobContainer = true;
}
I'll generally have an Initialize() method to set up some expensive instances that need to be stored in static member variables. I'll call Initialize() on each invocation, and use a check such as
_someMemberVariable ??= getItFromMyDiContainerOrInstantiateId();
So that it's only executed once, regardless of invocation count.

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.

Time trigger function with CosmosDB inputs

I have created a time trigger function in azure functions and Added a CosmosDB input as shown below.
Below is the .csx file
#r "Microsoft.Azure.Documents.Client"
using System;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
public static async Task Run(TimerInfo myTimer, string[] inputDocument, TraceWriter log)
{
log.Info($"C# Timer trigger function executed at: {DateTime.Now}");
// string [] = bindings.inputDocument;
DocumentClient client;
}
How to get the input documents from cosmosDb into this csx file?
I am not familiar with C#, in javascript we will use var Data = context.bindings.DataInput;
How to do the same in c#?
You can use it like the below snippet
public static void Run(TimerInfo myTimer, IEnumerable<dynamic> documents)
{
foreach (var doc in documents)
{
// operate on each document
}
}
More examples in documentation
Questions from comments
If we have more than one cosmos Db input do we need to add as below ?
No if even if you have more than one inputs the IEnumerable<dynamic> documents is used. And you can iterate the list.
How to add if we have a cosmosDB output ?
The out object is used in this which points to your binding.
public static void Run(string myQueueItem, out object employeeDocument, ILogger log)
{
log.LogInformation($"C# Queue trigger function processed: {myQueueItem}");
dynamic employee = JObject.Parse(myQueueItem);
employeeDocument = new {
id = employee.name + "-" + employee.employeeId,
name = employee.name,
employeeId = employee.employeeId,
address = employee.address
};
}
More information on Output

Adding Custom Dimension to Request Telemetry - Azure functions

I am creating a new Function app using v2.x and I am integrating Application Insights for request logging that is automatically being done as Azure Function is now integrated with App Insights (as mentioned in the documentation link). What I would need to do is log few custom fields in the custom dimensions in Application Insights Request Telemetry. Is it possible without using Custom Request logging (using TrackRequest method)
About adding custom properties, you could refer to this tutorial:Add properties: ITelemetryInitializer. The below is my test a HTTP trigger function.
public static class Function1
{
private static string key = "Your InstrumentationKey";
private static TelemetryClient telemetry = new TelemetryClient() { InstrumentationKey = key };
[FunctionName("Function1")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
if (!telemetry.Context.Properties.ContainsKey("Function_appName"))
{
telemetry.Context.Properties.Add("Function_appName", "testfunc");
}
else
{
telemetry.Context.Properties["Function_appName"] = "testfunc";
}
telemetry.TrackEvent("eventtest");
telemetry.TrackTrace("tracetest");
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");
}
}
After running this function, go to the Application Insights Search could check the data Or go to Logs(Analytics).
Update:
You should use ITelemetry Initializer(which can add custom dimension to a specified telemetry like only for request) in function app, please follow the steps below:
1.In Visual studio, create a function app(In my test, I create a blob triggerd function), and install the following nuget packages:
Microsoft.ApplicationInsights, version 2.10.0
Microsoft.NET.Sdk.Functions, version 1.0.29
2.Then in the Function1.cs, write code like below:
using Microsoft.ApplicationInsights.Channel;
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.IO;
[assembly: WebJobsStartup(typeof(FunctionApp21.MyStartup))]
namespace FunctionApp21
{
public static class Function1
{
[FunctionName("Function1")]
public static void Run([BlobTrigger("samples-workitems/{name}", Connection = "AzureWebJobsStorage")]Stream myBlob, string name, ILogger log)
{
log.LogInformation($"C# Blob trigger function Processed blob\n Name:{name} \n Size: {myBlob.Length} Bytes");
}
}
internal class MyTelemetryInitializer : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
//use telemetry is RequestTelemetry to make sure only add to request
if (telemetry != null && telemetry is RequestTelemetry && !telemetry.Context.GlobalProperties.ContainsKey("my_custom_dimen22"))
{
telemetry.Context.GlobalProperties.Add("my_custom_dimen22", "Hello, this is custom dimension for request!!!");
}
}
}
public class MyStartup : IWebJobsStartup
{
public void Configure(IWebJobsBuilder builder)
{
builder.Services.AddSingleton<ITelemetryInitializer, MyTelemetryInitializer>();
}
}
}
3.Publish it to azure, then nav to azure portal -> the published function app -> Monitor -> Add an application insights.
4.Run the function from azure. And wait for a few minutes -> nav to the application insights portal, check the telemetry data, and you can see the custom dimension is only added to request telemetry:
The other solutions don't quite answer the question, how to add custom properties to the request telemetry. There is a very simple solution, add the following within your function's code:
Activity.Current?.AddTag("my_prop", "my_value");
You'll need:
using System.Diagnostics;
This then can be dynamic per function invocation / request, rather a fixed global property.

Azure Function Blob Trigger - Container Name as Setting

I'm trying to create an Azure function that connects to a container that is not hard-coded, much like my connection.
public static void Run(
[BlobTrigger("Container-Name/ftp/{name}", Connection = "AzureWebJobsStorage")]Stream blobStream,
string name,
IDictionary<string, string> metaData,
TraceWriter log)
The connection property is able to get the connection value directly from local.settings.json. It does not appear that capability is an option for "container-name", or if so, not with the appended /ftp/{name}.
So, is there a way to set the container name based on settings for an Azure Function Blob Trigger?
You can define your function like this:
public static void Run(
[BlobTrigger("%mycontainername%/ftp/{name}", Connection = "AzureWebJobsStorage")]
Stream blobStream,
string name,
IDictionary<string, string> metaData,
TraceWriter log)
and then define an application setting called mycontainername to contain the actual container name.

Resources