Azure Function used to write to queue - can I set metadata? - c#

I can see from this page that you can access a queue message metadata properties simply enough when they are used as a trigger, but I want to do the opposite.
I have an Azure function which writes messages to a queue, but it current has the default Expiration Time and I want to set a much shorter expiration time so they only live on the queue for a very short period.
Is there a way when writing the message to the queue from the Azure Function to set the Expiration time?
Thanks
EDIT 1:
One caveat is that I dont know the name of the queue ahead of time. That is part of the incoming message, so the queuename is set as a parameter of the output binding
I made the change as recommended by #Mikhail. Here is the function as it stands:
#r "Microsoft.WindowsAzure.Storage"
#r "Newtonsoft.Json"
using System;
using Microsoft.WindowsAzure.Storage.Queue;
using Newtonsoft.Json;
public static void Run(MyType myEventHubMessage, CloudQueue outputQueue, TraceWriter log)
{
var deviceId = myEventHubMessage.DeviceId;
var data = JsonConvert.SerializeObject(myEventHubMessage);
var msg = new CloudQueueMessage(data);
log.Info($"C# Event Hub trigger function processed a message: {deviceId}");
outputQueue.AddMessage(msg, TimeSpan.FromMinutes(3), null, null, null);
}
public class MyType
{
public string DeviceId { get; set; }
public double Field1{ get; set; }
public double Field2 { get; set; }
public double Field3 { get; set; }
}
And the output binding in my function.json:
{
"type": "CloudQueue",
"name": "$return",
"queueName": "{DeviceId}",
"connection": "myConn",
"direction": "out"
}

Change the type of your parameter to CloudQueue, then add a message manually and set the expiration time property (or rather Time To Live).
public static void Run(string input, CloudQueue outputQueue)
{
outputQueue.AddMessage(
new CloudQueueMessage("Hello " + input),
TimeSpan.FromMinutes(5));
}
Edit: if your output queue name depends on request, you can use imperative binding:
public static void Run(string input, IBinder binder)
{
string outputQueueName = "outputqueue " + input;
QueueAttribute queueAttribute = new QueueAttribute(outputQueueName);
CloudQueue outputQueue = binder.Bind<CloudQueue>(queueAttribute);
outputQueue.AddMessage(
new CloudQueueMessage("Hello " + input),
TimeSpan.FromMinutes(5));
}

Related

Execute different pieces of code based on a value in C#

I have a piece of C# code that reacts to an HTTP trigger from Azure and gets executed. The piece of code sends an email containing a warning:
#r "Newtonsoft.Json"
#r "SendGrid"
using System;
using SendGrid.Helpers.Mail;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
public static SendGridMessage Run(HttpRequest req, ILogger log)
{
string requestBody = new StreamReader(req.Body).ReadToEnd();
log.LogInformation(requestBody);
var notifications = JsonConvert.DeserializeObject<IList<Notification>>(requestBody);
SendGridMessage message = new SendGridMessage();
message.Subject = "Sensor Anomaly Warning";
var content = "The following device has started registering anomalies:<br/><br/><table><tr><th>time</th><th>value</th><th>lower bound</th><th>upper bound</th><th>device</th></tr>";
foreach(var notification in notifications) {
log.LogInformation($" - time: {notification.time}, value: {notification.value}, lowerbound: {notification.lowerbound}, upperbound: {notification.upperbound}, device: {notification.device}");
content += $"<tr><td>{notification.time}</td><td>{notification.value}</td><td>{notification.lowerbound}</td><td>{notification.upperbound}</td><td>{notification.device}</td></tr>";
}
content += "</table>";
message.AddContent("text/html", content);
return message;
}
public class Notification
{
public string time { get; set; }
public string value { get; set; }
public string lowerbound { get; set; }
public string upperbound { get; set; }
public string device { get; set; }
}
Now, I want to execute this same piece of code that sends an email, but based on the value of notification.alert which stores a zero if an anomaly started and a 1 if the alert stopped. Coming from python, this would be as easy as setting an "if else" statement, where the function is called in each case.
However for this C# code, there is no function to call. The piece of code sends an email but it's just creating a class. In any case, I'm wondering if I can use a "if else" statement in C# based on the value of notification.alert that in one case sends an email saying something like "the device has started registering anomalies" and in the other "the device has stopped registering anomalies". I just can't get to do this, as I have to address the notification object, which is already inside the class.
I must say that I am not a C# developer but a Python developer, hence the doubts.
You can use if/else statement or switch to update the email body based on notification.alert. this function is not sending the email it's just setting up a message for the email body text.
using System;
using SendGrid.Helpers.Mail;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
public static SendGridMessage Run(HttpRequest req, ILogger log)
{
string requestBody = new StreamReader(req.Body).ReadToEnd();
log.LogInformation(requestBody);
var notifications = JsonConvert.DeserializeObject<IList<Notification>>(requestBody);
SendGridMessage message = new SendGridMessage();
message.Subject = "Sensor Anomaly Warning";
var content = "The following device has started registering anomalies: <br/><br/><table><tr><th>time</th><th>value</th><th>lower bound</th><th>upper bound</th><th>device</th></tr>";
foreach(var notification in notifications) {
log.LogInformation($" - time: {notification.time}, value: {notification.value}, lowerbound: {notification.lowerbound}, upperbound: {notification.upperbound}, device: {notification.device}");
content += notification.alert == 0 ? "the device has started registering anomalies" : "the device has stopped registering anomalies";
}
content += "</table>";
message.AddContent("text/html", content);
return message;
}
public class Notification
{
public string time { get; set; }
public string value { get; set; }
public string lowerbound { get; set; }
public string upperbound { get; set; }
public string device { get; set; }
public int alert { get; set;}
}

Azure function - GraphQLHttpClient with CreateSubscriptionStream

I have a requirement like I want to subscribe an GraphQLHttpClient using CreateSubscriptionStream into a function app .
stream.Subscribe(
response =>
{
Console.WriteLine($"RaceUpdatesSubscription message: \"{response.Data}\" ");
SendMsgToTopic.SendMessageToTopicAsync(response.Data, _serviceBusConnectionString, _raceUpdatesTopicName).Wait();
},
exception => Console.WriteLine($"message RaceUpdatesSubscription stream failed: {exception}"),
() => Console.WriteLine($"message RaceUpdatesSubscription stream completed")
);
How can I achieve that Like is there any way to user any trigger from azure function or register this for subscription (Any changes happen then I need to listen that)
Now its working fine with console app but trying to use function app.
To CreateSubscriptionStream into a function app. Below are the details to subscribe
// To use NewtonsoftJsonSerializer, add a reference to NuGet package GraphQL.Client.Serializer.Newtonsoft
var graphQLClient = new GraphQLHttpClient("https://api.example.com/graphql", new NewtonsoftJsonSerializer());
Below is the code to use subscriptions
public class UserJoinedSubscriptionResult {
public ChatUser UserJoined { get; set; }
public class ChatUser {
public string DisplayName { get; set; }
public string Id { get; set; }
}
}
Below is the code how to create subscription
var userJoinedRequest = new GraphQLRequest {
Query = #"
subscription {
userJoined{
displayName
id
}
}"
};
IObservable<GraphQLResponse<UserJoinedSubscriptionResult>> subscriptionStream
= client.CreateSubscriptionStream<UserJoinedSubscriptionResult>(userJoinedRequest);
var subscription = subscriptionStream.Subscribe(response =>
{
Console.WriteLine($"user '{response.Data.UserJoined.DisplayName}' joined")
});
To get complete information on CreateSubscriptionStream refer this GraphQL

Sending input parameters to AWS Lambda function from Unity

I'm learning AWS Lambda with C#. My function looks sort of like this:
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
namespace Function_Redeem
{
public class Function
{
public FunctionOutput FunctionHandler(FunctionInput input, ILambdaContext context)
{
// do work with input
// return FunctionOutput
}
public class FunctionInput
{
public string someData { get; set; }
}
public class FunctionOutput
{
public string someAnswer { get; set; }
}
}
}
It works fine when using the Test button in AWS, as well as the test feature in Visual Studio.
Now, I'm trying to call this from Unity.
So first, I added an API Gateway trigger, and left the defaults:
API endpoint: [the url]
API type: HTTP
Authorization: NONE
Cross-origin resource sharing (CORS): No
Enable detailed metrics: No
Method: ANY
Resource path: /FunctionName
Stage: default
Then in Unity,
private static IEnumerator TestFunction(string uri, string data)
{
UnityWebRequest webRequest = UnityWebRequest.Put(uri, data);
yield return webRequest.SendWebRequest();
if (webRequest.isNetworkError)
Debug.LogError("Network error: " + webRequest.error);
else
Debug.Log(webRequest.downloadHandler.text);
}
I call it, with data being
{"someData":"Hello"}
The function call works, I know that it is reaching my function, but the input data (i.e. the someData field) is null. It seems like it's not parsing the data I'm sending so FunctionInput defaults to null someData.
What am I missing?
Since you are using API Gateway as a trigger to your lambda function, accept APIGatewayProxyRequest as input parameter to your handler(instead of FunctionInput). The field Body would have your serialized payload {"someData":"Hello"}
public class Function
{
public FunctionOutput FunctionHandler(APIGatewayProxyRequest request, ILambdaContext context)
{ var requestBody = JsonConvert.DeserializeObject<FunctionInput>(request.Body);
// do work with input
// return FunctionOutput
}
public class FunctionInput
{
public string someData { get; set; }
}
public class FunctionOutput
{
public string someAnswer { get; set; }
}
}
}

Azure WebJobs access configuration from within a static function

I'm creating a webjob in .net core 3.1. In this project I have a function that is timer activated which should read the number of messages in a queue Q1 and if empty, put a message in Q2 as well as trigger a rest call to an API.
In order to check how many messages are in the API I need to access the AzureWebJobsStorage in my appsettings.json and then the url which is also in the settings.
Program.cs
class Program
{
static async Task Main()
{
var builder = new HostBuilder();
builder.ConfigureWebJobs(b =>
{
b.AddAzureStorageCoreServices();
b.AddAzureStorage();
b.AddTimers();
});
builder.ConfigureLogging((context, b) =>
{
b.AddConsole();
});
builder.ConfigureAppConfiguration((context, b) =>
{
b.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
});
builder.ConfigureServices((context, services) =>
{
var mySettings = new MySettings
{
AzureWebJobsStorage = context.Configuration.GetValue<string>("AzureWebJobsStorage"),
AzureWebJobsDashboard = context.Configuration.GetValue<string>("AzureWebJobsDashboard"),
url = context.Configuration.GetValue<string>("url"),
};
services.AddSingleton(mySettings);
});
var host = builder.Build();
using (host)
{
await host.RunAsync();
}
}
}
Fuctions.cs
public class Functions
{
public static void UpdateChannels([QueueTrigger("Q1")] string message, ILogger logger)
{
logger.LogInformation(message);
}
public static void WhatIsThereToUpdate([QueueTrigger("Q2")] string message, ILogger logger)
{
logger.LogInformation(message);
}
public static void CronJob([TimerTrigger("0 * * * * *")] TimerInfo timer, [Queue("Q2")] out string message, ILogger logger, MySettings mySettings)
{
message = null;
// Get the connection string from app settings
string connectionString = mySettings.AzureWebJobsStorage;
logger.LogInformation("Connection String: " + connectionString);
// Instantiate a QueueClient which will be used to create and manipulate the queue
QueueClient queueClient = new QueueClient(connectionString, "Q1");
if (queueClient.Exists())
{
QueueProperties properties = queueClient.GetProperties();
// Retrieve the cached approximate message count.
int cachedMessagesCount = properties.ApproximateMessagesCount;
// Display number of messages.
logger.LogInformation($"Number of messages in queue: {cachedMessagesCount}");
if (cachedMessagesCount == 0)
message = "Hello world!" + System.DateTime.Now.ToString(); //here I would call the REST API as well
}
logger.LogInformation("Cron job fired!");
}
}
appsettings.json
{
"AzureWebJobsStorage": "constr",
"AzureWebJobsDashboard": "constr",
"url": "url"
}
My Settings
public class MySettings
{
public string AzureWebJobsStorage { get; set; }
public string AzureWebJobsDashboard { get; set; }
public string url { get; set; }
}
However when I run this I get the following error:
Error indexing method 'Functions.CronJob'
Microsoft.Azure.WebJobs.Host.Indexers.FunctionIndexingException: Error indexing method 'Functions.CronJob'
---> System.InvalidOperationException: Cannot bind parameter 'mySettings' to type MySettings. Make sure the parameter Type is supported by the binding. If you're using binding extensions (e.g. Azure Storage, ServiceBus, Timers, etc.) make sure you've called the registration method for the extension(s) in your startup code (e.g. builder.AddAzureStorage(), builder.AddServiceBus(), builder.AddTimers(), etc.).
In addition to what is shown in the above codes I also tried using ConfigurationManager and Environment.GetEnvironmentVariable, both methods gave me null when I tried to read the values. For example ConfigurationManager.AppSettings.GetValues("AzureWebJobsStorage").
I also tried to register IConfiguration as a service services.AddSingleton(context.Configuration); and inject it in the parameters (instead of MySettings), but it also gave me the same binding error.
I'm really at a loss here, I've scoured the SO archives trying to find a solution and I think I tried everything I saw gave people positive results, but unfortunately I wasn't as lucky as the other posters.
Any guidance is much appreciated.
Edited to add my packages
In case it helps anyone, I'm using the following
Azure.Storage.Queues (12.4.0)
Microsoft.Azure.WebJobs.Extensions (3.0.6)
Microsoft.Azure.WebJobs.Extensions.Storage (4.0.2)
Microsoft.Extensions.Logging.Console (3.1.7)
When using DI, I suggest you use non-static method and constructor inject.
Here is the Functions.cs:
public class Functions
{
private readonly MySettings mySettings;
public Functions(MySettings _mySettings)
{
mySettings = _mySettings;
}
public void ProcessQueueMessage([TimerTrigger("0 */1 * * * *")] TimerInfo timer, [Queue("queue")] out string message, ILogger logger)
{
message = null;
string connectionString = mySettings.AzureWebJobsStorage;
logger.LogInformation("Connection String: " + connectionString);
}
}
No code change in other .cs file.
Here is the test result:

How to Enable/Disable Azure Function programmatically

Is there a way to programmatically enable/disable an Azure function?
I can enable/disable a function using the portal under the "Manage" section, which causes a request to be sent to https://<myfunctionapp>.scm.azurewebsites.net/api/functions/<myfunction>
The JSON payload looks a bit like:
{
"name":"SystemEventFunction",
"config":{
"disabled":true,
"bindings":[
// the bindings for this function
]
}
// lots of other properties (mostly URIs)
}
I'm creating a management tool outside of the portal that will allow users to enable and disable functions.
Hoping I can avoid creating the JSON payload by hand, so I'm wondering if there is something in an SDK (WebJobs??) that has this functionality.
Further to #James Z.'s answer, I've created the following class in C# that allows you to programmatically disable / enable an Azure function.
The functionsSiteRoot constructor argument is the Kudu root of your Functions application, eg https://your-functions-web-app.scm.azurewebsites.net/api/vfs/site/wwwroot/
The username and password can be obtained from "Get publish profile" in the App Service settings for your Functions.
public class FunctionsHelper : IFunctionsHelper
{
private readonly string _username;
private readonly string _password;
private readonly string _functionsSiteRoot;
private WebClient _webClient;
public FunctionsHelper(string username, string password, string functionsSiteRoot)
{
_username = username;
_password = password;
_functionsSiteRoot = functionsSiteRoot;
_webClient = new WebClient
{
Headers = { ["ContentType"] = "application/json" },
Credentials = new NetworkCredential(username, password),
BaseAddress = functionsSiteRoot
};
}
public void StopFunction(string functionName)
{
SetFunctionState(functionName, isDisabled: true);
}
public void StartFunction(string functionName)
{
SetFunctionState(functionName, isDisabled: false);
}
private void SetFunctionState(string functionName, bool isDisabled)
{
var functionJson =
JsonConvert.DeserializeObject<FunctionSettings>(_webClient.DownloadString(GetFunctionJsonUrl(functionName)));
functionJson.disabled = isDisabled;
_webClient.Headers["If-Match"] = "*";
_webClient.UploadString(GetFunctionJsonUrl(functionName), "PUT", JsonConvert.SerializeObject(functionJson));
}
private static string GetFunctionJsonUrl(string functionName)
{
return $"{functionName}/function.json";
}
}
internal class FunctionSettings
{
public bool disabled { get; set; }
public List<Binding> bindings { get; set; }
}
internal class Binding
{
public string name { get; set; }
public string type { get; set; }
public string direction { get; set; }
public string queueName { get; set; }
public string connection { get; set; }
public string accessRights { get; set; }
}
No, this is not possible currently. The disabled metadata property in function.json is what determines whether a function is enabled. The portal just updates that value when you enable/disable in the portal.
Not sure if it will meet your needs, but I'll point out that there is also a host.json functions array that can be used to control the set of functions that will be loaded (documented here). So for example, if you only wanted 2 of your 10 functions enabled, you could set this property to an array containing only those 2 function names (e.g. "functions": [ "QueueProcessor", "GitHubWebHook" ]), and only those will be loaded/enabled. However, this is slightly different than enable/disable in that you won't be able to invoke the excluded functions via the portal, whereas you can portal invoke disabled functions.
Further to #DavidGouge 's answer above, the code he posted does work, I just tested it and will be using it in my app. However it needs a couple of tweaks:
Remove the inheritance from IFunctionsHelper. I'm not sure what that interface is but it wasn't required.
Change the class definition for Binding as follows:
internal class Binding
{
public string name { get; set; }
public string type { get; set; }
public string direction { get; set; }
public string queueName { get; set; }
public string connection { get; set; }
public string accessRights { get; set; }
public string schedule { get; set; }
}
After that it would work.
P.S. I would have put this as a comment on the original answer, but I don't have enough reputation on Stack Overflow to post comments!
Using a combination of #Satya V's and #DavidGouge's solutions, I came up with this:
public class FunctionsHelper
{
private readonly ClientSecretCredential _tokenCredential;
private readonly HttpClient _httpClient;
public FunctionsHelper(string tenantId, string clientId, string clientSecret, string subscriptionId, string resourceGroup, string functionAppName)
{
var baseUrl =
$"https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.Web/sites/{functionAppName}/";
var httpClient = new HttpClient
{
BaseAddress = new Uri(baseUrl)
};
_httpClient = httpClient;
_tokenCredential = new ClientSecretCredential(tenantId, clientId, clientSecret);
}
private async Task SetAuthHeader()
{
var accessToken = await GetAccessToken();
_httpClient.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse($"Bearer {accessToken}");
}
private async Task<string> GetAccessToken()
{
return (await _tokenCredential.GetTokenAsync(
new TokenRequestContext(new[] {"https://management.azure.com/.default"}))).Token;
}
public async Task StopFunction(string functionName)
{
await SetFunctionState(functionName, isDisabled: true);
}
public async Task StartFunction(string functionName)
{
await SetFunctionState(functionName, isDisabled: false);
}
private async Task SetFunctionState(string functionName, bool isDisabled)
{
await SetAuthHeader();
var appSettings = await GetAppSettings();
appSettings.properties[$"AzureWebJobs.{functionName}.Disabled"] = isDisabled ? "1" : "0";
var payloadJson = JsonConvert.SerializeObject(new
{
kind = "<class 'str'>", appSettings.properties
});
var stringContent = new StringContent(payloadJson, Encoding.UTF8, "application/json");
await _httpClient.PutAsync("config/appsettings?api-version=2019-08-01", stringContent);
}
private async Task<AppSettings> GetAppSettings()
{
var res = await _httpClient.PostAsync("config/appsettings/list?api-version=2019-08-01", null);
var content = await res.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<AppSettings>(content);
}
}
internal class AppSettings
{
public Dictionary<string, string> properties { get; set; }
}
The problem with using the Kudu api to update the function.json file is that it will be overwritten on any subsequent deploy. This uses Azure's Rest Api to update the Configuration of the application. You will first need an Azure Service Principle to use the api though.
Using the Azure Cli, you can run az ad sp create-for-rbac to generate the Service Principle and get the client id and client secret. Because the UpdateConfiguration endpoint does not allow you to update a single value, and overwrites the entire Configuration object with the new values, you must first get all the current Configuration values, update the one you want, and then call the Update endpoint with the new Configuration keys and values.
I would imagine you can use Kudu REST API (specifically VFS) to update the disabled metadata property in function.json. Would that disable the function?
Here is the Kudu REST API. https://github.com/projectkudu/kudu/wiki/REST-API
The CLI command That is used to disable the Azure function through CLI - documented here
az functionapp config appsettings set --name <myFunctionApp> \
--resource-group <myResourceGroup> \
--settings AzureWebJobs.QueueTrigger.Disabled=true
I had captured fiddler while while running the above command.
Azure CLI works on the Python process The python process was issuing request to
https://management.azure.com to update appsetting.
got a reference to the same endpoint in the below REST Endpoint :
https://learn.microsoft.com/en-us/rest/api/appservice/webapps/updateapplicationsettings
Request URI :
PUT
https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/config/appsettings?api-version=2019-08-01
Headers :
Authorization: Bearer <> ;
Content-Type: application/json; charset=utf-8
Request Body:
{"kind": "<class 'str'>", "properties":JSON}
We can hardcode the properties or get it dynamically. For disabling the function, will have to update the JSON node of Properties : Azure.WebJobs.QueueTrigger.Disabled = True
To get properties you could use the endpoint, you could refer Web Apps - List Application Settings
The Output looks up as below :
Hope this helps :)
What about this: https://learn.microsoft.com/en-us/azure/azure-functions/disable-function?tabs=portal#localsettingsjson
This looks like the easiest solution for local development.

Categories