Dynamically enable/ disable a triggered function in Azure WebJob - c#

We have an azure web job that has two methods in the Functions.cs file. Both jobs are triggered off different Topics in Azure Service Bus.
As this uses reflection at run time to determine the functions that are to be run/triggered by messages hitting the topics, there are no references to these methods in code.
public static async Task DoWork([ServiceBusTrigger("topic-one", "%environmentVar%")] BrokeredMessage brokeredMessage, TextWriter log) {}
public static async Task DoOtherWork([ServiceBusTrigger("topic-two", "%environmentVar2%")] BrokeredMessage brokeredMessage, TextWriter log) {}
I have a need to have this web job run either both methods, or just one of them, based on a variable set at run time (it won't change one the job is running, but it is read in when the job starts). I can't simply wrap the internals of the methods in an if() based on the variable, as that would read and destroy the message.
Is it possible to use the JobHostConfiguration (an IServiceProvider) to achieve this, as that is built at run time. Is that was the JobHostConfiguration.IJobActivator can be used for?

Triggered Functions can be disabled when the Webjob starts.
You can have a look at this issue: Dynamic Enable/ Disable a function.
So the Webjob SDK provided a DisableAttribute`:
Can be applied at the Parameter/Method/Class level
Only affects triggered functions
[Disable("setting")] - If a config/environment value exists for the specified setting name, and its value is "1" or "True" (case insensitive), the function will be disabled.
[Disable(typeof(DisableProvider))] - custom Type declaring a function of signature bool IsDisabled(MethodInfo method). We'll call this method to determine if the function should be disabled.
This is a startup time only check. For disabled triggered functions, we simply skip starting of the function listener. However, when you update app settings bound to these attributes, your WebJob will automaticallly restart and your settings will take effect.
Setting names can include binding parameters (e.g. {MethodName}, {MethodShortName}, %test%, etc.)
In your case you need to use the DisableAttribute with a DisableProvider.
public class DoWorkDisableProvider
{
public bool IsDisabled(MethodInfo method)
{
// check if the function should be disable
// return true or false
return true;
}
}
public class DoOtherWorkkDisableProvider
{
public bool IsDisabled(MethodInfo method)
{
// check if the function should be disable
// return true or false
return true;
}
}
And your functions should be decorated with the disable attribute
[Disable(typeof(DoWorkDisableProvider))]
public static async Task DoWork([ServiceBusTrigger("topic-one", "%environmentVar%")] BrokeredMessage brokeredMessage, TextWriter log) {}
[Disable(typeof(DoOtherWorkkDisableProvider))]
public static async Task DoOtherWork([ServiceBusTrigger("topic-two", "%environmentVar2%")] BrokeredMessage brokeredMessage, TextWriter log) {}
Otherwise the JobHostConfiguration.IJobActivator is designed to inject dependencies into your functions. you can have a look at these posts related to:
Dependency injection using Azure WebJobs SDK?
Azure Triggered Webjobs Scope for Dependency Injection

Related

What is the simplest way to run a single background task from a controller in .NET Core?

I have an ASP.NET Core web app, with WebAPI controllers. All I am trying to do is, in some of the controllers, be able to kick off a process that would run in the background, but the controller should go ahead and return before that process is done. I don't want the consumers of the service to have to wait for this job to finish.
I have seen all of the posts about IHostedService and BackgroundService, but none of them seem to be what I want. Also, all these examples show you how to set things up, but not how to actually call it, or I am not understanding some of it.
I tried these, but when you register an IHostedService in Startup, it runs immediately at that point in time. This is not what I want. I don't want to run the task at startup, I want to be able to call it from a controller when it needs to. Also, I may have several different ones, so just registering services.AddHostedService() won't work because I might have a MyServiceB and MyServiceC, so how do I get the right one from the controller (I can't just inject IHostedService)?
Ultimately, everything I have seen has been a huge, convoluted mess of code for something that seems like it should be such a simple thing to do. What am I missing?
You have the following options:
IHostedService classes can be long running methods that run in the background for the lifetime of your app. In order to make them to handle some sort of background task, you need to implement some sort of "global" queue system in your app for the controllers to store the data/events. This queue system can be as simple as a Singleton class with a ConcurrentQueue that you pass in to your controller, or something like an IDistributedCache or more complex external pub/sub systems. Then you can just poll the queue in your IHostedService and run certain operations based on it. Here is a microsoft example of IHostedService implementation for handling queues https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&tabs=visual-studio#queued-background-tasks
Note that the Singleton class approach can cause issues in multi-server environments.
Example implementation of the Singleton approach can be like:
// Needs to be registered as a Singleton in your Startup.cs
public class BackgroundJobs {
public ConcurrentQueue<string> BackgroundTasks {get; set;} = new ConcurrentQueue<string>();
}
public class MyController : ControllerBase{
private readonly BackgroundJobs _backgroundJobs;
public MyController(BackgroundJobs backgroundJobs) {
_backgroundJobs = backgroundJobs;
}
public async Task<ActionResult> FireAndForgetEndPoint(){
_backgroundJobs.BackgroundTasks.Enqueue("SomeJobIdentifier");
}
}
public class MyBackgroundService : IHostedService {
private readonly BackgroundJobs _backgroundJobs;
public MyBackgroundService(BackgroundJobs backgroundJobs)
{
_backgroundJobs = backgroundJobs;
}
public void StartAsync(CancellationToken ct)
{
while(!ct.IsCancellationRequested)
{
if(_backgroundJobs.BackgroundTasks.TryDequeue(out var jobId))
{
// Code to do long running operation
}
Task.Delay(TimeSpan.FromSeconds(1)); // You really don't want an infinite loop here without having any sort of delays.
}
}
}
Create a method that returns a Task, pass in a IServiceProvider to that method and create a new Scope in there to make sure ASP.NET would not kill the task when the controller Action completes. Something like
IServiceProvider _serviceProvider;
public async Task<ActionResult> FireAndForgetEndPoint()
{
// Do stuff
_ = FireAndForgetOperation(_serviceProvider);
Return Ok();
}
public async Task FireAndForgetOperation(IServiceProvider serviceProvider)
{
using (var scope = _serviceProvider.CreateScope()){
await Task.Delay(1000);
//... Long running tasks
}
}
Update: Here is the Microsoft example of doing something similar: https://learn.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?view=aspnetcore-3.1#do-not-capture-services-injected-into-the-controllers-on-background-threads
As I understand from your question you want to create a fire and forget task like logging to database. In this scenario you don't have to wait for log to be inserted database. It also took much of my time to discover an easily implementable solution. Here is what I have found:
In your controller parameters, add IServiceScopeFactory. This will not effect the request body or header. After that create a scope and call your service over it.
[HttpPost]
public IActionResult MoveRecordingToStorage([FromBody] StreamingRequestModel req, [FromServices] IServiceScopeFactory serviceScopeFactory)
{
// Move record to Azure storage in the background
Task.Run(async () =>
{
try
{
using var scope = serviceScopeFactory.CreateScope();
var repository = scope.ServiceProvider.GetRequiredService<ICloudStorage>();
await repository.UploadFileToAzure(req.RecordedPath, key, req.Id, req.RecordCode);
}
catch(Exception e)
{
Console.WriteLine(e);
}
});
return Ok("In progress..");
}
After posting your request, you will immediately receive In Progress.. text but your task will run in the background.
One more thing, If you don't create your task in this way and try to call database operations you will receive an error like this which means your database object is already dead and you are trying to access it;
Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.\r\nObject name: 'DBContext'.
My code is based on Repository pattern. You should not forget to inject service class in your Startup.cs
services.AddScoped<ICloudStorage, AzureCloudStorage>();
Find the detailed documentation here.
What is the simplest way to run a single background task from a controller in .NET Core?
I don't want the consumers of the service to have to wait for this job to finish.
Ultimately, everything I have seen has been a huge, convoluted mess of code for something that seems like it should be such a simple thing to do. What am I missing?
The problem is that ASP.NET is a framework for writing web services, which are applications that respond to requests. But as soon as your code says "I don't want the consumers of the service to have to wait", then you're talking about running code outside of a request (i.e., request-extrinsic code). This is why all solutions are complex: your code has to bypass/extend the framework itself in an attempt to force it to do something it wasn't designed to do.
The only proper solution for request-extrinsic code is to have a durable queue with a separate background process. Anything in-process (e.g., ConcurrentQueue with an IHostedService) will have reliability problems; in particular, those solutions will occasionally lose work.

Azure Durable Function error handling: Is there a way to identify which retry you are on?

The Durable Functions documentation specifies the following pattern to set up automatic handling of retries when an exception is raised within an activity function:
public static async Task Run(DurableOrchestrationContext context)
{
var retryOptions = new RetryOptions(
firstRetryInterval: TimeSpan.FromSeconds(5),
maxNumberOfAttempts: 3);
await ctx.CallActivityWithRetryAsync("FlakyFunction", retryOptions, "ABC");
// ...
}
However I can't see a way to check which retry you're up to within the activity function:
[FunctionName("FlakyFunction")]
public static string[] MyFlakyFunction(
[ActivityTrigger] string id,
ILogger log)
{
// Is there a built-in way to tell what retry attempt I'm up to here?
var retry = ??
DoFlakyStuffThatMayCauseException();
}
EDIT: I know it can probably be handled by mangling some sort of count into the RetryOptions.Handle delegate, but that's a horrible solution. It can be handled manually by maintaining an external state each time it's executed, but given that there's an internal count of retries I'm just wondering if there's any way to access that. Primary intended use is debugging and logging, but I can think of many other uses.
There does not seem to be a way to identify the retry. Activity functions are unaware of state and retries. When the CallActivityWithRetryAsync call is made the DurableOrchestrationContext calls the ScheduleWithRetry method of the OrchestrationContext class inside the DurableTask framework:
public virtual Task<T> ScheduleWithRetry<T>(string name, string version, RetryOptions retryOptions, params object[] parameters)
{
Task<T> RetryCall() => ScheduleTask<T>(name, version, parameters);
var retryInterceptor = new RetryInterceptor<T>(this, retryOptions, RetryCall);
return retryInterceptor.Invoke();
}
There the Invoke method on the RetryInterceptor class is called and that does a foreach loop over the maximum number of retries. This class does not expose properties or methods to obtain the number of retries.
Another workaround to help with debugging could be logging statements inside the activity function. And when you're running it locally you can put in a breakpoint there to see how often it stops there. Note that there is already a feature request to handle better logging for retries. You could add your feedback there or raise a new issue if you feel that's more appropriate.
To be honest, I think it's good that an activity is unaware of state and retries. That should be the responsibility of the orchestrator. It would be useful however if you could get some statistics on retries to see if there is a trend in performance degradation.

Benefits of an async ServiceBusTrigger

I'm working on microservices (using Azure Function Apps) that contain ServiceBusTrigger-based Azure Functions that trigger when a message is inserted into a Service Bus Queue.
I'm trying to determine the best way of binding output values to multiple targets (e.g. CosmosDB and IoT Hub). Whether or not the method is marked as async will determine how I should approach this problem.
As far as I am aware, the way that you would typically handle output binding with an async function is by using the [return: ...] annotation; however, in my use case, I need to return two different values to two separate targets (e.g. CosmosDb and IoT Hub). I don't think that this is something that I can achieve with return value binding or output variable binding, since you can't have an out param with an async method and you can define multiple return values with the [return: ...] approach.
It would seem that my only option (if I went the async route) would be to manually invoke SDK methods in the Azure Function to call the services independent of any output values. I'm trying to avoid doing that, seeing as output binding is the preferred approach.
An observation that I have made when creating a brand new ServiceBusTrigger-based Azure Function is that the generated method signature is not marked as async by default.
This is different than an HttpTrigger, which is marked as async out-of-box.
Can someone help me understand the reasoning for this? What are the scaling implications associated with one vs. the other?
I understand in a traditional sense why you typically mark an HttpTrigger as async; however, I don't understand the reasoning as to why the ServiceBusTrigger is not async
I need to understand this bit before I can move on with solidifying my approach to outputs.
I don't think templates with/without async functions have any reasoning to them as such. And depending on your code, your function may be more efficient.
Read this thread for more details on async/await in functions.
As for your main question, you just have to bind to different objects for the CosmosDB and IoT Hub output bindings.
For CosmosDB, you will have to bind to IAsyncCollector instead as shown in the docs
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace CosmosDBSamplesV2
{
public static class WriteDocsIAsyncCollector
{
[FunctionName("WriteDocsIAsyncCollector")]
public static async Task Run(
[QueueTrigger("todoqueueforwritemulti")] ToDoItem[] toDoItemsIn,
[CosmosDB(
databaseName: "ToDoItems",
collectionName: "Items",
ConnectionStringSetting = "CosmosDBConnection")]
IAsyncCollector<ToDoItem> toDoItemsOut,
ILogger log)
{
log.LogInformation($"C# Queue trigger function processed {toDoItemsIn?.Length} items");
foreach (ToDoItem toDoItem in toDoItemsIn)
{
log.LogInformation($"Description={toDoItem.Description}");
await toDoItemsOut.AddAsync(toDoItem);
}
}
}
}
For Event Hub, you will have to bind to IAsyncCollector instead as shown in the docs
[FunctionName("EH2EH")]
public static async Task Run(
[EventHubTrigger("source", Connection = "EventHubConnectionAppSetting")] EventData[] events,
[EventHub("dest", Connection = "EventHubConnectionAppSetting")]IAsyncCollector<string> outputEvents,
ILogger log)
{
foreach (EventData eventData in events)
{
// do some processing:
var myProcessedEvent = DoSomething(eventData);
// then send the message
await outputEvents.AddAsync(JsonConvert.SerializeObject(myProcessedEvent));
}
}

How to get an ILogger from an Activity Function?

I am using Durable Azure Function in a prototype for a future project.
Basically, I have a Client Azure Function triggered by an HTTP POST request that starts the Orchestrator. Then, the Orchestrator decides to trigger an Activity. Nothing complicated.
Here is a sample of what I am doing:
[FunctionName("ClientFunction")]
public static async Task<HttpResponseMessage> OnHttpTriggerAsync([HttpTrigger(AuthorizationLevel.Anonymous, "post")]
HttpRequestMessage request, [OrchestrationClient] DurableOrchestrationClient starter, ILogger logger)
{
// Triggers the orchestrator.
string instanceId = await starter.StartNewAsync("OrchestratorFunction", null);
return new HttpResponseMessage(HttpStatusCode.OK);
}
[FunctionName("OrchestratorFunction")]
public static async Task DoOrchestrationThingsAsync([OrchestrationTrigger] DurableOrchestrationContext context, ILogger logger)
{
// Triggers some serious activity.
await context.CallActivityAsync("ActivityFunction", null);
}
[FunctionName("ActivityFunction")]
public static Task DoAnAwesomeActivity([ActivityTrigger] DurableActivityContext context)
{
// Short & Sweet activity...
// Wait... Where's my logger?
}
Both the Orchestrator and the Client Functions are being given an ILogger but not the Activity Function; as stated in the documentation (either a specific parameter or the DurableActivityContext instance), the Activity function only gets one parameter. And I am not under the impression that the static class in which these methods are declared could keep a reference on that ILogger.
I understand that the Activity Function should perform one small job but I would be more comfortable if I was able to log that the activity was called with the appropriate values if something goes wrong (and it will :) ).
Question
How can the Activity access the ILogger?
It is not possible to pass multiple parameters to an activity function directly. The recommendation in this case is to pass in an array of objects or to use ValueTuples objects in .NET.
This restriction you are concerned about is talking about the parameters we pass from Orchestrator to Activity Function. It doesn't mean we could only use one parameter in Activity method signature. Feel free to add ILogger there and complete your job as needed.

How to bind to MessageSender in an Azure function?

I'm working with a service bus queue and an Azure function. Failures are handled differently depending on some business logic. Some messages needs to sent to the dead letter queue, others need to be modified then explicitly added back to the queue.
Consider the following code:
[FunctionName("ServiceBusTask")]
public static async Task Run(
[ServiceBusTrigger("myQueue", Connection = "myConnectionString")]
Message message,
MessageReceiver messageReceiver,
//MessageSender messageSender,
ILogger log)
{
//some business logic that can fail
if( condition for dead letter)
{
await messageReceiver.DeadLetterAsync(message.SystemProperties.LockToken);
}
else if( condition for a manual retry)
{
QueueClient qc = new Queueclient("myQueue", "myConnectionString");
Message updatedMessage = GetUpdatedMessage(message);
await qc.SendAsync(updatedMessage);
//await messageSender.SendAsync(updatedMessage);
}
}
The messageReceiver works just fine to send messages to the dead letter queue but it bothers me that I need to create a QueueClient to send messages to the queue. Knowing that MessageSender exists, I tried to add it to the parameters but I'm getting an error saying:
Cannot bind parameter 'messageSender' to type MessageSender. 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.).
I'm not too sure why it's telling me about startup code, I have no such thing so I'm guessing the error message wasn't updated...
Reading this issue on the Azure webjobs SDK, I get the impression that it should be supported (do correct me if I'm reading it wrong!).
My question
Is it possible to use MessageSender like this and if so, what do I need to do to make it work?
Your function is using a ServiceBusTrigger, which supports MessageReceiver, but not the MessageSender binding - that's supported by the ServiceBus output binding, which you could add to your function. (example)

Categories

Resources