Can someone describe me how I can configure a C# azure function which uses an HTTP input trigger and a blob storage output trigger?
Maybe also with an example code snippet and an example function.json. I don't get it to work locally with the azure functions core tools.
This is a combined HTTP triggered function with a output blob binding:
[FunctionName("HttpTriggeredFunction")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest httpRequest,
[Blob("blobcontainer", Connection = "StorageConnectionString")] CloudBlobContainer outputContainer,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
await outputContainer.CreateIfNotExistsAsync();
var requestBody = await new StreamReader(httpRequest.Body).ReadToEndAsync();
var blobName = Guid.NewGuid().ToString();
var cloudBlockBlob = outputContainer.GetBlockBlobReference(blobName);
await cloudBlockBlob.UploadTextAsync(requestBody);
return new OkObjectResult(blobName);
}
It uses the CloudBlobContainer output type to get a reference to the blob container which then enables you to use methods such as .GetBlockBlobReference("blobPath") to get a reference to a blob.
Once you have a reference to a blob, you can use different methods to upload:
cloudBlockBlob.UploadFromByteArrayAsync()
cloudBlockBlob.UploadFromFileAsync()
cloudBlockBlob.UploadTextAsync()
cloudBlockBlob.UploadFromStreamAsync()
To get it running locally, you need set some things up. Notice in my example the attribute [Blob("blobcontainer", Connection = "StorageConnectionString")]
"blobcontainer" this can be whatever you want and will be the name of the container that will be created in your storage account by this line outputContainer.CreateIfNotExistsAsync(); (if it doesn't exist already).
Connection = "StorageConnectionString" this can be a setting in your local.settings.json for the connection string of your storage account. When developing locally I would recommend setting this to "UseDevelopmentStorage=true" so that you can take advantage of the storage emulator. Then when you are ready to deploy onto Azure, you would create a setting in the function app containing the real connection string.
local.settings.json
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet",
"StorageConnectionString": "UseDevelopmentStorage=true"
}
}
to make an http function that saves to Blob Storage use this code:
#r "Newtonsoft.Json"
using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
public static async Task<IActionResult> Run(HttpRequest req, ILogger log,TextWriter outputBlob)
{
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
outputBlob.WriteLine(requestBody);
string result = "{ 'result': 'ok' }";
dynamic data = JsonConvert.DeserializeObject(result);
return new OkObjectResult(data);
}
You need to set the output binding:
You can then run a test posting content on the test window
Everything you need is there in the Official docs page,
(i) Http and WebHooks
(ii)Output binding blob storage
Http Trigger Sample code
[FunctionName("HttpTriggerCSharp")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]
HttpRequest req, ILogger log)
Blob Storage Output binding
[FunctionName("ResizeImage")]
public static void Run(
[BlobTrigger("sample-images/{name}")] Stream image,
[Blob("sample-images-sm/{name}", FileAccess.Write)] Stream imageSmall,
[Blob("sample-images-md/{name}", FileAccess.Write)] Stream imageMedium)
Related
I saw the Microsoft documentation for Azure Service Bus output binding for Azure Functions version 3. When I want to send a message to a service bus as return of the function, I can use this code:
[FunctionName("ServiceBusOutput")]
[return: ServiceBus("myqueue", Connection = "ServiceBusConnection")]
public static string ServiceBusOutput([HttpTrigger] dynamic input, ILogger log)
{
log.LogInformation($"C# function processed: {input.Text}");
return input.Text;
}
My problem started when I want to have as an output 2 messages to different Service Bus. Is it possible to output binding more than one output? In the online editor you can add more than one output. How can I do that in the code?
In the documentation there is a section of Usage, it explains what I can use as output binding. They mention ICollector<T> or IAsyncCollector<T> but I'm not sure it is what I'm looking for.
Other question is what happens in an API that returns one value to the bus and another to the user?
You could have two IAsyncCollector<T> output bindings:
[FunctionName("HttpTriggeredFunction")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
[ServiceBus("queuename1", Connection = "ServiceBusConnectionString1")] IAsyncCollector<dynamic> outputServiceBus1,
[ServiceBus("queuename2", Connection = "ServiceBusConnectionString2")] IAsyncCollector<dynamic> outputServiceBus2,
ILogger log)
{
await outputServiceBus1.AddAsync("Item1");
await outputServiceBus2.AddAsync("Item2");
return new OkObjectResult(null);
}
local.settings.json:
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet",
"ServiceBusConnectionString1": "Endpoint=sb://sb1.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=",
"ServiceBusConnectionString2": "Endpoint=sb://sb2.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey="
}
}
Other question is what happens in an API that returns one value to the bus and another to the user?
In the above example, it is returning (adding) a message to the Service Bus and returning a IActionResult (HTTP response) to the user.
You could write to a single service bus topic that has multiple subscribers
I'm creating an Azure Function to process files. The Azure Function will be activated with a HTTP Trigger, so the function should be executed whenever a page from a site makes a HTTP request to it.
The Function will take some time to finish processing the files, but I can't get the site waiting for the Function to finish to know If everything was ok. So what I want is some kind of "received" message from the Azure Function just to know that it received the HTTP request, before it starts processing.
Is there any way to do that with a HTTP Trigger? Can I let the caller know that its request was correctly received and, after that, start executing the Azure Function?
Yes, it's super easy to do that using Durable Functions:
1- Install 'Microsoft.Azure.WebJobs.Extensions.DurableTask' nuget package;
2-
[FunctionName("Function1")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
[DurableClient] IDurableOrchestrationClient starter,
ILogger log)
{
Guid instanceId = Guid.NewGuid();
string x = await starter.StartNewAsync("Processor", instanceId.ToString(), null);
log.LogInformation($"Started orchestration with ID = '{instanceId}'.");
return starter.CreateCheckStatusResponse(req, x);
}
3-
[FunctionName("Processor")]
public static async Task<string> Search([OrchestrationTrigger] IDurableOrchestrationContext context)
{
var output= await context.CallActivityAsync<string>("DoSomething", null);
return output;
}
[FunctionName("DoSomething")]
public static async Task<string> Execute([ActivityTrigger] string termo, ILogger log)
{
//do your work in here
}
In the previous code we're creating an Orchestrator (Processor) function, and it will start an activity which will do the process DoSomething function.
More info: https://learn.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-overview
I have default C# based HTTP Trigger here and I wish to send data "Hello Name" to Service Bus Topic (already created). I'm coding at portal.
How to do it Service Bus output binding?
This is not working. Any help available?
-Reference missing for handling Service Bus?
-How to define Connection of service bus? Where is Functions.json
-How to send a message to service bus?
//This FunctionApp get triggered by HTTP and send message to Azure Service Bus
using System;
using System.IO;
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 Newtonsoft.Json;
namespace Company.Function
{
public static class HttpTriggerCSharp1
{
[FunctionName("HttpTriggerCSharp1")]
[return: ServiceBus("myqueue", Connection = "ServiceBusConnection")] // I added this for SB Output. Where to define.
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.");
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);
// I added this for SB Output
return responseMessage;
}
}
}
Firstly, there are two bindings to send data to service bus. Firstly is what you show, using the return binding, after install two packages Microsoft.Azure.WebJobs.Extensions.ServiceBus and WindowsAzure.ServiceBus, then you will be able to send data. And you could not do it cause your function type is IActionResult and you are trying to return string(responseMessage).
So if you want to send the whole responseMessage, just return new OkObjectResult(responseMessage);, it will work. And the result would be like below pic.
And if you want to use return responseMessage; should change your method type to string, it will be public static async Task<string> RunAsync and result will be below.
Another binding you could refer to below code or this sample.
[FunctionName("Function1")]
[return: ServiceBus("myqueue", Connection = "ServiceBusConnection")]
public static async Task RunAsync(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
[ServiceBus("myqueue", Connection = "ServiceBusConnection")] MessageSender messagesQueue,
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;
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.";
byte[] bytes = Encoding.ASCII.GetBytes(responseMessage);
Message m1 = new Message(bytes);
await messagesQueue.SendAsync(m1);
}
How to define Connection of service bus? Where is Functions.json
In the local you should define the connection in the local.settings.jon, you could use any name with the connection, then in the binding Connection value should be the name you set in the json file. And cause you are using c#, so you could not modify the function.json file, there will be a function.json file in the debug folder. So you could only change the binding in the code.
Hope this could help you, if you still have other problem , please feel free to let me know.
Make sure you first install Microsoft.Azure.WebJobs.Extensions.ServiceBus NuGet package. Then make sure you are using it in your project:
using Microsoft.Azure.WebJobs.Extensions.ServiceBus;
Make sure you clean and build the project to make sure you have no errors.
Then you need to make sure you have a "ServiceBusConnection" connection string inside your local.settings.json file:
{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "dotnet",
"ServiceBusConnection": "Endpoint=sb://...",
}
}
Which you can get if you go to Azure portal -> Service bus namespace -> Shared access policies -> RootManageSharedAccessKey -> Primary Connection String. Copy and paste this connection string inside "ServiceBusConnection". You can also use the Secondary Connection String as well.
Note: Service bus queues/topics have shared access policies as well. So if you don't want to use the Service bus namespace level access policies, you can create one at queue/topic level, so you your function app only has access to the queue/topic defined in your namespace.
Also if you decide to publish your function app, you will need to make sure you create a configuration application setting for "ServiceBusConnection", since local.settings.json is only used for local testing.
I have a simple azure function as below
public static class Function1
{
[FunctionName("Function1")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
[Table("MyTable")] CloudTable cloudTable,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
var entity = new CustomerEntity("Jon", "Snow")
{
Email = "babc#email.com",
PhoneNumber = "04026753432"
};
// Create the InsertOrReplace table operation
TableOperation insertOrMergeOperation = TableOperation.InsertOrMerge(entity);
// Execute the operation.
TableResult result = await cloudTable.ExecuteAsync(insertOrMergeOperation);
CustomerEntity insertedCustomer = result.Result as CustomerEntity;
// Get the request units consumed by the current operation. RequestCharge of a TableResult is only applied to Azure Cosmos DB
if (result.RequestCharge.HasValue)
{
Console.WriteLine("Request Charge of InsertOrMerge Operation: " + result.RequestCharge);
}
return new OkObjectResult($"Hello");
}
}
I am using Microsoft.Azure.Cosmos.Table instead of Microsoft.WindowsAzure.Storage.Table. However the "Table" attribute is not being recognize. Is there any extension nuget that I need to include here ?
-Alan-
It sounds like you may be trying to mix and match SDKs. The binding that you are using in the function is not using the Cosmos version of the table SDK, so you either need to use the storage one outright, or not leverage a binding and just use the Cosmos Table SDK directly to create your own instance of a cloudTable
Add Microsoft.Azure.WebJobs.Extensions.Storage package and your done
I have an Azure function like this
public async Task<IActionResult> GetProducts
(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "products/{prod}/all")] HttpRequest req,
[Blob("%prodPath%", FileAccess.Read, Connection = "AzureWebJobsStorage")] Stream myBlobRead,
ILogger log, string prod)
{
var result = await _function.get(myBlobRead).ConfigureAwait(false);
return result;
}
)
The blob reads the file path from config json file.
Inside the config json file, there is a variable and a value like this:
"prodPath": "products/{prod}-all.csv"
if the user do a get request like this => www.xxx.com/api/products/table/all
everything is fine because the blob file name called table-all.csv
but if the user tries to do a get request like this => www.xxx.com/api/products/Table/all, its will fail becuase the name does'nt match.
Can you please help me with that?
I tried to change the variable in the config file to
[tolower("prodPath")]: "products/{prod}-all.csv"
but same problem.
How to change the prod to lowercase ?
Thank you
I may suggest using the runtime binding technique (article for c# and c# script). The idea is that instead of using the blog attribute you use the IBinder parameter and then invoke this binding in your function body. Obviously, you can now evaluate any parameter you want. Note, that you should remove the expression from the json file if you use this technique.
Here is a sample from MSDN combined with bit of your code:
public static class IBinderExample
{
[FunctionName("CreateBlobUsingBinder")]
public static void Run(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "products/{prod}/all")] HttpRequest req,
IBinder binder)
{
var prodPath = $"products/{prod}-all.csv".ToLower();
// provide connection string and optionally change TextWriter to whatever you need
using (var writer = binder.Bind<TextWriter>(new BlobAttribute(
prodPath, FileAccess.Write)))
{
writer.Write("Hello World!");
};
}
}