I have a durable function that is triggered once a day by a Timer Trigger:
[FunctionName("MyDurableFunction")]
public static async Task Run(
[TimerTrigger("0 0 23 * * *", RunOnStartup = false)] TimerInfo myTimer,
[OrchestrationClient] DurableOrchestrationClient starter,
ILogger log)
{
await starter.StartNewAsync("OrchestrationFunction", null);
}
[FunctionName("OrchestrationFunction")]
public static async Task OrchestrationFunction(
[OrchestrationTrigger]DurableOrchestrationContext context,
ILogger log)
{
// do stuff
}
This works fine. For testing purposes I would also like to be able to trigger the durable function via a Http Trigger, so I added this:
[FunctionName("MyDurableFunctionHttpTrigger")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "demo")]HttpRequest req,
[OrchestrationClient] DurableOrchestrationClient starter,
ILogger log)
{
await starter.StartNewAsync("OrchestrationFunction", null);
return new OkObjectResult("blah");
}
Running these locally, including either the http trigger or the timer trigger will trigger the function, but including both in the class means that neither trigger events will occur. Is it possible to have multiple trigger types start an orchestration trigger?
I believe you can only have one trigger type per function but can suggest you write all your logic in to a separate project/assembly and then just reference the assembly and call the entry point via parameters, keeping your function implementation clean and simple and centralising the execution logic in another project (or classes within the same project).
On your code, you should have Orchestrator and Activity functions, so you could write one Activity function to do the work and call it from two orchestrators. The guidance on Durable Functions is to keep the orchestrator clean and simple managing just that - the orchestration, offloading the work to the Activities.
I recommend you look at the durable monitor pattern for your timer based requirement and look at the HTTP APIs for HTTP Triggers.
What you could do is create multiple normal functions, one for each type of trigger. A scheduled trigger, http trigger, blob trigger, or any other supported trigger.
Within that function you can start a new orchestration function. That orchestration function does not require a trigger in itself. You only need the DurableOrchestrationContext.
public static async Task<object> RunOrchestrator(
[OrchestrationTrigger] DurableOrchestrationContext context,
ILogger log)
{
// orchestration logic here
}
[FunctionName("Info_HttpStart1")]
public static async Task<HttpResponseMessage> HttpStart(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "starter1")]HttpRequestMessage req,
[OrchestrationClient]DurableOrchestrationClient starter,
ILogger log)
{
// Function input comes from the request content.
string instanceId = await starter.StartNewAsync("Info", null);
log.LogInformation($"Started orchestration with ID = '{instanceId}'.");
return starter.CreateCheckStatusResponse(req, instanceId);
}
[FunctionName("Info_HttpStart2")]
public static async Task<HttpResponseMessage> HttpStart(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "starter2")]HttpRequestMessage req,
[OrchestrationClient]DurableOrchestrationClient starter,
ILogger log)
{
// Function input comes from the request content.
string instanceId = await starter.StartNewAsync("Info", null);
log.LogInformation($"Started orchestration with ID = '{instanceId}'.");
return starter.CreateCheckStatusResponse(req, instanceId);
}
Related
I haven't written any Azure functions in quite a long time, and thought I'd refresh myself today, but I've clearly forgotten how to write appropriate unit tests for them. I have the following Function - it picks a random quote from a list;
public class QuoteFunction
{
private readonly IQuoteBank _repository;
public QuoteFunction(IQuoteBank repository)
{
_repository = repository;
}
[FunctionName("GetQuote")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
Quote quote = _repository.getQuote();
return new OkObjectResult(quote);
}
}
and it uses dependency injection to obtain the list of quotes - I have the following in Startup;
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddSingleton<IQuoteBank, QuoteBank>();
builder.Services.AddLogging();
}
which is injected into the constructor of the Function. as shown in the first snippet.
What I am struggling with is how I can use Moq to force the quote (which is randomly selected) to be consistent. I know I can mock the Interface IQuoteBank - but there is no where I can pass this mock object into the Run method.
So what I want to know is how I can pass a mock object to make the same quote be produced for unit testing? Has anyone done anything like this before? any examples in github?
I'm pretty sure I did a few years ago, just cant remember at all.
Setup the mock and pass that into the subject under test via constructor injection.
public async Task MyTestMehod() {
// Arrange
Mock<IQuoteBank> mock = new Mock<IQuoteBank>();
mock.Setup(_ => _.getQuote()).Returns("my consistent quote here")
var subject = new QuoteFunction(mock.Object);
//Act
IActionResult result = await subject.Run(Mock.Of<HttpRequest>(), Mock.Of<ILogger>());
//Assert
// ... assert my expected behavior
}
Is it possible that 2 azure functions can get triggered by one eventhub? One azure function will write its data to database1 and the other azure function writes its data to database2
[FunctionName("EventToDB1")]
public async System.Threading.Tasks.Task Run([EventHubTrigger("eventhub", Connection = "Debezium")]
EventData[] events, ILogger log)
{
{
[FunctionName("EventToDB2")]
public async System.Threading.Tasks.Task Run([EventHubTrigger("eventhub", Connection = "Debezium")]
EventData[] events, ILogger log)
{
{
answer on the possibility of having 2 azure functions get triggered by one eventhub
Yes that is possible by using different consumer groups. Because you specified the same connection to the Event Hub, being "Debezium", I Assume you want both funtions to process the same message. You have to create a new consumer group and specify the name using the ConsumerGroup property of the EventHubTrigger attribute (The default consumergroup is $Default):
public class EventToDB1
{
[FunctionName("EventToDB1")]
public async System.Threading.Tasks.Task Run(
[EventHubTrigger("eventhub",
Connection = "Debezium",
ConsumerGroup = "CG1")]
EventData[] events, ILogger log)
{
}
}
public class EventToDB2
{
[FunctionName("EventToDB2")]
public async System.Threading.Tasks.Task Run(
[EventHubTrigger("eventhub",
Connection = "Debezium",
ConsumerGroup = "CG2")]
EventData[] events, ILogger log)
{
}
}
Each consumer group receives the same messages from the Event Hub.
I do agree with #peter bons, you need to create two consumer groups for that and you can create two consumers by below process:
You can also use logic apps to work with event hubs.
I am using AzureFunctions.Autofac to inject into my Azure Functions web api. An example of the config:
public class DIConfig
{
public DIConfig()
{
DependencyInjection.Initialize(builder =>
{
// DAL
builder.Register<IDbContext>(c => new SecretCompanyContext()).InstancePerLifetimeScope();
builder.RegisterType<SecretCompanyContext>().InstancePerLifetimeScope();
builder.RegisterType<SecretCompanyContext>().As<ICartContext>().InstancePerLifetimeScope();
builder.RegisterGeneric(typeof(Repository<>)).As(typeof(IRepository<>)).InstancePerLifetimeScope();
// Services
builder.RegisterType<InventoryServices>().As<IInventoryServices>().InstancePerLifetimeScope();
// Controllers ported from ASP.NET MVC Web API
builder.RegisterType<InventoryController>().InstancePerLifetimeScope();
});
}
Then my Azure functions, I have one class that defines all methods in the API
[DependencyInjectionConfig(typeof(DIConfig))]
public class InventoryFunctions : FunctionsApi
{
[FunctionName("GetProductsByCategory")]
// /inventory/categories/{id}/products
public static async Task<HttpResponseMessage> GetProductsByCategory(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "inventory/categories/{id}/products")]
HttpRequestMessage req,
TraceWriter log,
int id,
[Inject] InventoryController controller)
{
// do stuff
var result = await controller.GetProductsByCategory(id);
return JsonResponse(result, HttpStatusCode.OK);
}
[FunctionName("GetInventoryBySku")]
// /inventory/skus?sku=ASDF&sku=ASDG&sku=ASDH
public static async Task<HttpResponseMessage> GetInventoryBySku(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "inventory")]
HttpRequestMessage req,
TraceWriter log,
[Inject] InventoryController controller)
{
// do stuff
var result = await controller.QueryInventoryBySkuList(skuList);
return JsonResponse(result, HttpStatusCode.OK);
}
[FunctionName("UpdateProductsQuantity")]
// /inventory
// Post
public static async Task<HttpResponseMessage> UpdateProductsQuantity(
[HttpTrigger(AuthorizationLevel.Function, "put", Route = "inventory")]
HttpRequestMessage req,
TraceWriter log,
[Inject] InventoryController controller)
{
// do stuff
var inventoryProducts = await req.Content.ReadAsAsync<List<InvProductOperation>>();
var result = await controller.UpdateAvailableProductsQuantity(inventoryProducts);
return JsonResponse(result, HttpStatusCode.OK);
}
But I keep getting this error:
A second operation started on this context before a previous
asynchronous operation completed. Use 'await' to ensure that
any asynchronous operations have completed before calling
another method on this context. Any instance members are not
guaranteed to be thread safe.
I have verified that async and await are used properly, so following the error message's recommendation isn't fixing it. What appears to be the issue is that IDbContext is not honoring the InstancePerLifetimeScope as expected. Is this happening because I have more than one method in my InventoryFunctions class? Or is AzureFunctions.Autofac not threadsafe?
Change the registration of the DbContext to this:
builder.Register<IDbContext>(c => new SecretCompanyContext()).InstancePerDependency();
You can find a deeper explanation of mine for why this is happening here.
I was going by this SO answer: Autofac - InstancePerHttpRequest vs InstancePerLifetimeScope which said that InstancePerLifetimeScope was the non-ASP.NET equivalent of InstancePerRequest.
I spoke to the developers and they said the truth is that getting one DbContext per HttpRequest was the default behavior when you simply register using builder.RegisterType<SecretCompanyContext>.As<IDbContext>() so there's some misinformation out there.
So the solution is, instead of using
builder.Register<IDbContext>(c => new SecretCompanyContext()).InstancePerDependency();
or
builder.RegisterType<SecretCompanyContext>().As<IDbContext>().InstancePerLifetimeScope();
one should just use
builder.RegisterType<SecretCompanyContext>().As<IDbContext>();
if the goal is one instance per HTTP request.
I have such initial azure function:
[FunctionName("TriggerNoResponseFunction")]
public static async Task Run(
[TimerTrigger("0 */1 * * * *")] TimerInfo info,
[OrchestrationClient] DurableOrchestrationClient starter)
My Orchestrator function:
[FunctionName("NoResponseOrchestrator")]
public static async Task Run(
[OrchestrationTrigger] DurableOrchestrationContext context,
ILogger log)
And an activity trigger:
[FunctionName("NoResponseHandlerFunction")]
public static void Run(
[ActivityTrigger] DbReadOptions readOptions)
But in this case my Activity trigger function gets called a lot of times intead of CRON expression which as i understand should restrict count of callings. What i'm doing wrong. How to achieve durable function to get called only by timer schedule?
Thanks for help in advance
I would like to get the context id of current Azure Function execution to be included in the content of the response if there's any error during execution. My intention is to help me during troubleshooting by quickly find the traces of respective execution with its id. Here is what the code looks like:
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
try
{
// Some code...
}
catch (Exception ex)
{
return new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = new StringContent("Insert Azure Function Context Id here...");
};
}
}
And here is how the context id looks like in Azure Function monitor:
Is it possible to get the context id of the current Azure Function execution? If yes, how can I get it?
This should give you,
public static HttpResponseMessage Run(HttpRequestMessage req, TraceWriter log, ExecutionContext context)
{
return req.CreateResponse(System.Net.HttpStatusCode.OK, context.InvocationId);
}