I am trying to create Xunit test for azure timer triggered functions and I have to pass the service bus client queue in Run method and I cannot mock a service bus queue and not sure how to pass as a parameter
Below is my azure function
[FunctionName("TimerJob")]
public static async Task Run([TimerTrigger("%TimerJobExpression%")] TimerInfo myTimer,
[ServiceBus("%Queue1%", Connection = "ServiceBusConnection")] IAsyncCollector<string> Queue1,
[ServiceBus("%Queue2%", Connection = "ServiceBusConnection")] IAsyncCollector<string> Queue2,
ILogger log)
{
//logic
}
Below is the test method I am trying to create
[Fact]
public async Task Run_Queue()
{
var param1 = default(TimerInfo);
var result = await ProducerClientTimerJob.Run(param1, null, null, logger);
}
As sellotape mentioned in the comment, you just need to give a IAsyncCollector object.
That is an output binding and the information have been stored in the attribute so you don't need to give anything input information of the service bus. just give a IAsyncCollector object is ok.
Related
I have the following services implemented on Azure:
1x Azure SignalR Service (Serverless) ASRS
2x Azure Functions (Serverless) HubFuncDown & HubFuncUp
On the ASRS I have defined TWO UpStream URLs, one to HubFuncDown Azure Function and the other to HubFuncUp. Using the URL pattern defined in the docs:
HubFuncDown Contains the following method which instructs the device app to disable itself:
[FunctionName(nameof(DisableDevice))]
public async Task DisableDevice([SignalRTrigger] InvocationContext invocationContext, string deviceId, ILogger logger) {
await Clients.User(deviceId).SendAsync(DisableDeviceTarget, new NewMessage(invocationContext, deviceId));
}
And HubFuncUp Contains the following method:
[FunctionName(nameof(DeviceDisabled))]
public async Task DeviceDisabled([SignalRTrigger] InvocationContext invocationContext, string deviceId, ILogger logger) {
// .. Updates DBContext, sends alerts etc
}
I'm trying to tell HubFuncUp that this client is now in a disabled state, the code to do that on the (UWP) client is:
var connection = new HubConnectionBuilder()
.WithUrl("https://hubfuncup.azurewebsites.net/api", options => {
options.AccessTokenProvider = () => Auth();
})
.ConfigureLogging(logging => {
logging.AddProvider(new SerilogLoggerProvider());
logging.SetMinimumLevel(LogLevel.Debug);
})
.WithAutomaticReconnect(new RetryPolicy())
.Build();
And to tell HubFuncUp of the state:
await connection.InvokeAsync("UpdateDeviceState", new DisabledDeviceMessage { DeviceId = 123, State = States.Disabled });
But each time I'm receiving a 404 Error from the call to InvokeAsync("UpdateDeviceState"..). It seems no matter what I do I cannot connect the one ASRS to two Azure Functions using multiple Upstream URLs.
Am I correct in thinking I need to utilise a seperate Azure SignalR Service (which doubles my cost) or can I connect the two Functions to the one SignalR Service via routing on the Upstream URLs?
I have 2 Web APIs developed on ASP.NET Core. The idea is: the WebAPI_1 sends a message to the Azure Service Bus and then WebAPI_2 has to catch this moment and read it shortly after the message is sent. I understand how to catch this moment is I have a console app instead of WebAPI_2, but I am not sure how to subscribe WebAPI_2 on such the event happening in Azure Service Bus.
Below is the code where I have WebAPI_1 and the Console App.
WebAPI_1 (Sender):
public class QueueService : IQueueService
{
private readonly IConfiguration _config;
public QueueService(IConfiguration config)
{
_config = config;
}
public async Task SendMessageAsync<T>(T serviceBusMessage, string queueName)
{
var queueClient = new QueueClient(_config.GetConnectionString("AzureServiceBus"), queueName);
string messageBody = JsonSerializer.Serialize(serviceBusMessage);
var message = new Message(Encoding.UTF8.GetBytes(messageBody));
await queueClient.SendAsync(message);
}
}
And this is how I send it:
await queue.SendMessageAsync(obj, "myqueue");
And this is the Console App (Receiver):
Main(){
queueClient = new QueueClient(connectionString, queueName);
var messageHandlerOptions = new MessageHandlerOptions(ExceptionReceivedHandler)
{
MaxConcurrentCalls = 1,
AutoComplete = false
};
queueClient.RegisterMessageHandler(ProcessMessagesAsync, messageHandlerOptions);
}
private static async Task ProcessMessagesAsync(Message message, CancellationToken token)
{
var jsonString = Encoding.UTF8.GetString(message.Body);
Model obj = JsonSerializer.Deserialize<Model>(jsonString);
Console.WriteLine($"Person Received: { obj.Field1} { obj.Field2}");
await queueClient.CompleteAsync(message.SystemProperties.LockToken);
}
But I want WebAPI_2 to be able to receive the messages instead of the Console App.
Please advise.
Receiving messages requires a continuous job. ASP.NET Core Controller, as you've probably found out, is not the right place as it's not running continuously and is intended to respond to the request. For a continuous execution, a background service, or task, is the right option. ASP.NET Core has an option to run a BackgroundService that could be used for exactly what you need.
There are multiple blog posts with the details in case you want to get some inspiration:
Getting Started With Azure Service Bus Queues And ASP.NET Core Background Services
Using An ASP.NET Core IHostedService To Run Azure Service Bus Subscriptions and Consumers
I’m developing a app that used CosmosDB to store data and then when anyone updates the data i want the clients to be updated.
For this i have decided to use the changefeed and then Azure Functions and Azure SignalR.
I have set up 2 functions.
A negotiate function (This one works and the clients connect correctly to the SignalR server)
And a OnDocumentsChanged function, and my problem is getting the function to actually sending the message, when something is changed.
I have the following function:
[FunctionName("OnDocumentsChanged")]
public static async Task Run(
[CosmosDBTrigger(
databaseName: "NewOrder",
collectionName: "NewOrder",
CreateLeaseCollectionIfNotExists = true,
ConnectionStringSetting = "myserver_DOCUMENTDB",
LeaseCollectionName = "leases")]
IReadOnlyList<Document> updatedNewOrder,
[SignalR(ConnectionStringSetting = "AzureSignalRConnectionString", HubName = "NewOrder")] IAsyncCollector<SignalRMessage> signalRMessages,
ILogger log)
{
if (updatedNewOrder != null && updatedNewOrder.Count > 0)
{
foreach (var Orders in updatedNewOrder)
{
await signalRMessages.AddAsync(new SignalRMessage
{
Target = "NewOrderUpdated",
Arguments = new[] { Orders.Id }
});
}
}
}
I can see that it is correctly triggered when a change is made to the database, but no messages are send.
I guess I’m missing a out part that actually send the SignalRMessages I’m just not sure how to implement.
Thanks.
I do not want to use service bus trigger using .net code http trigger function app to read messages from topic.
Run below code locally or deploy it on azure portal. send a message
into topic/sub it will read messages successfully.
wait for 1 minute
again send messages to topic/sub , it wont read messages from
topic/sub. if running locally keep fucntion running and make a http
call.
public static class Function1
{
[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.");
string serviceBusConnectionString = "connectionstring";
var messageReceiver = new MessageReceiver(serviceBusConnectionString, "topicName/subscriptions/subscriptionName, ReceiveMode.PeekLock, null, 500);
var messages = await messageReceiver.ReceiveAsync(500, TimeSpan.FromSeconds(1));
if (messages.Count > 0)
{
foreach (var item in messages)
{
await messageReceiver.CompleteAsync(item.SystemProperties.LockToken);
}
}
return new OkObjectResult("");
}
}
Is there another code which will work for .net code http trigger function app?
I create a bot, called picturesaver, using Microsoft's Bot Framework, I added a GroupMe channel, and I have it hosted in Azure. The bot works perfectly, saving pictures to Google Drive.
However, the bot gives an error saying "Service Error:POST to picturesaver timed out after 15s" Is it possible to extend the timeout time? Or even stop the bot from posting anything at all. Could this be an Azure issue or is it a GroupMe issue?
If your bot performs an operation that takes longer than 15 seconds to process a message, you can process the message on another thread, and acknowledge the call right away. Something like:
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
if ([determine if this will take > 15s])
{
// process the message asyncronously
Task.Factory.StartNew(async () => await Conversation.SendAsync(activity, () => new Dialogs.RootDialog()));
}
else
{
//process the message normally
await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
}
}
return Request.CreateResponse(HttpStatusCode.OK); //ack the call
}
This will avoid the 15 second timeout between connector and bot.
Edit: the above will not scale, and is just using a Task.Factory. Please refer to https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-howto-long-operations-guidance for the recommended guidance on processing long operations from a bot.
The Bot Connector service has a 15s timeout so you need to make sure any async API calls are handled in that timeframe, or make sure your bot responds with some kind of message if it's waiting for some other operation to complete. Currently the 15s timeout cannot be modified.
The solution to process the message on another thread, and acknowledge the call right away is good only for a bot on an App Service.
But as for a Functions Bot doing so will finish the Azure Function if I immediately return from this method.
I tried it. The Azure Function stops running, and the real response to the chat never comes. So it's not a solution at all for the Function Bots.
I ended up with this code for a Functions Bot, which resolves this problem.
Using Azure Queues
public static class Functions
{
[FunctionName("messages")]
[return: Queue("somequeue")]
public static async Task<MessagePayload> Messages([HttpTrigger
(WebHookType = "genericJson")]HttpRequestMessage req) =>
// return from this Azure Function immediately to avoid timeout warning message
// in the chat.
// just put the request into "somequeue".
// We can't pass the whole request via the Queue, so pass only what we need for
// the message to be processed by Bot Framework
new MessagePayload
{
RequestUri = req.RequestUri,
Content = await req.Content.ReadAsStringAsync(),
AuthScheme = req.Headers.Authorization.Scheme,
AuthParameter = req.Headers.Authorization.Parameter
};
// Do the actual message processing in another Azure Function, which is
// triggered by a message enqueued in the Azure Queue "somequeue"
[FunctionName("processTheMessage")]
public static async Task ProcessTheMessage([QueueTrigger("somequeue")]
MessagePayload payload, TraceWriter logger)
{
// we don't want the queue to process this message 5 times if it fails,
// so we won't throw any exceptions here at all, but we'll handle them properly.
try
{
// recreate the request
var request = new HttpRequestMessage
{
Content = new StringContent(payload.Content),
RequestUri = payload.RequestUri
};
request.Headers.Authorization = new
AuthenticationHeaderValue(payload.AuthScheme, payload.AuthParameter);
// initialize dependency injection container, services, etc.
var initializer = new SomeInitializer(logger);
initializer.Initialize();
// handle the request in a usual way and reply back to the chat
await initializer.HandleRequestAsync(request);
}
catch (Exception ex)
{
try
{
// TODO: handle the exception
}
catch (Exception anotherException)
{
// swallow any exceptions in the exceptions handler?
}
}
}
}
[Serializable]
public class MessagePayload
{
public string Content { get; set; }
public string AuthParameter { get; set; }
public string AuthScheme { get; set; }
public Uri RequestUri { get; set; }
}
(Be sure to use different Azure Queues for local development with Bot Framework emulator and for a cloud-deployed Function App. Otherwise, the messages sent to your bot from real customers may be processed locally while you are debugging on your machine)
Using an HTTP request
Of course, the same can be done without using an Azure Queue with a direct call to another Azure Function's public URL - https://<my-bot>.azurewebsites.net/api/processTheMessage?code=<function-secret>. This call has to be done on another thread, without waiting for the result in the messages function.
[FunctionName("messages")]
public static async Task Run([HttpTrigger(WebHookType = "genericJson")]
HttpRequestMessage req)
{
// return from this Azure Function immediately to avoid timeout warning message
// in the chat.
using (var client = new HttpClient())
{
string secret = ConfigurationManager.AppSettings["processMessageHttp_secret"];
// change the RequestUri of the request to processMessageHttp Function's
// public URL, providing the secret code, stored in app settings
// with key 'processMessageHttp_secret'
req.RequestUri = new Uri(req.RequestUri.AbsoluteUri.Replace(
req.RequestUri.PathAndQuery, $"/api/processMessageHttp?code={secret}"));
// don't 'await' here. Simply send.
#pragma warning disable CS4014
client.SendAsync(req);
#pragma warning restore CS4014
// wait a little bit to ensure the request is sent. It will not
// send the request at all without this line, because it would
// terminate this Azure Function immediately
await Task.Delay(500);
}
}
[FunctionName("processMessageHttp")]
public static async Task ProcessMessageHttp([HttpTrigger(WebHookType = "genericJson")]
HttpRequestMessage req,
Microsoft.Extensions.Logging.ILogger log)
{
// first and foremost: initialize dependency
// injection container, logger, services, set default culture/language, etc.
var initializer = FunctionAppInitializer.Initialize(log);
// handle the request in a usual way and reply back to the chat
await initializer.HandleRequest(req);
}