I have an azure function that uses a service bus trigger:
public async Task Run(
[ServiceBusTrigger()] string message,
[DurableClient] IDurableOrchestrationClient starter)
{
var response = JsonConvert.DeserializeObject<Response>(message);
await starter.StartNewAsync(nameof(OrchestratorFunction), response);
}
I need to update this to:
public async Task Run(
[ServiceBusTrigger()] Message message, MessageReceiver messageReceiver,
[DurableClient] IDurableOrchestrationClient starter)
{
var response = JsonConvert.DeserializeObject<Response>(Encoding.UTF8.GetString(message.Body));
var res = starter.StartNewAsync(nameof(OrchestratorFunction), response);
await messageReceiver.CompleteAsync(message.SystemProperties.LockToken);
await res (what do I update this line to?)
}
You can use start the orchestration with Await
# here you can get the Instance ID.
var res = await starter.StartNewAsync(nameof(OrchestratorFunction), response);
# so you can skip this part(You don't want to await the Instance ID )
await res (what do I update this line too?)
I have modified the code at the end which resembles completing the Async operation started in the Orchestrator function.
public async Task Run(
[ServiceBusTrigger()] Message message, MessageReceiver messageReceiver,
[DurableClient] IDurableOrchestrationClient starter)
{
var response = JsonConvert.DeserializeObject<Response>(Encoding.UTF8.GetString(message.Body));
# here you can get the Instance ID.
var res = await starter.StartNewAsync(nameof(OrchestratorFunction), response);
# It Completes the message and it will not receive again.
await messageReceiver.CompleteAsync(message.SystemProperties.LockToken);
# This can be done only if the MessageReceiver is created in ReceiveMode.PeekLock mode (which is default).
await messageReceiver.CloseAsync();
# skip this part it won't required because while calling the StartNewAsync we call the await.
//await res (what do I update this line too?)
}
If you want to get the status of your orchestrator function you can use the await starter.GetStatusAsync(< orchestration instanceId>);. After the asynchronous operation of the orchestration client which means after the code of await starter.StartNewAsync().
var orchestrationStatus = await starter.GetStatusAsync(<orchestration instanceId>);
var status = orchestrationStatus.RuntimeStatus.ToString().ToUpper();
while (status == "PENDING" || status == "RUNNING")
{
await Task.Delay(1000);
orchestrationStatus = await starter.GetStatusAsync(< orchestration instanceId>);
status = orchestrationStatus.RuntimeStatus.ToString().ToUpper();
}
Refer here for more information
Related
I have a Blazor Server app that calls a number of APIs. Everything works fine, but I am trying to wrap these calls in Tasks. As soon as my code gets to the call, everything just stops. I am sure I am doing something stupid, but no end of Googling is finding me the solution. The call comes from a Syncfusion Grid when selecting a row. Here is my minimum reproducable code:
public static IEnumerable<Quotation> customerQuotations = Array.Empty<Quotation>();
public async Task CustomerRowSelectHandler(RowSelectEventArgs<Customer> args)
{
GetCustomerQuotes(args.Data.customerId);
}
static async void GetCustomerQuotes(int customerId)
{
string url = string.Format(#"https://my.server.dns/quotations/customer/{0}", customerId);
var task = GetJsonString(url);
task.Wait();
customerQuotations = (IEnumerable<Quotation>)JsonConvert.DeserializeObject<Quotation>(task.Result);
}
private static async Task<string> GetJsonString(string url)
{
var TCS = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
var myClient = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });
HttpResponseMessage response = await myClient.GetAsync(url);
string streamResponse = await response.Content.ReadAsStringAsync();
TCS.SetResult(streamResponse);
return await TCS.Task;
}
If I do this how I am doing all of my other calls, i.e. not using a Task, it works fine, so I know it's not a silly error, it's something I am missing in my Task call.
Thanks in anticipation of any help offered.
The main problem, is Task.Wait(). That can deadlock.
public async Task CustomerRowSelectHandler(RowSelectEventArgs<Customer> args)
{
//GetCustomerQuotes(args.Data.customerId);
await GetCustomerQuotes(args.Data.customerId);
}
//static async void GetCustomerQuotes(int customerId)
async Task GetCustomerQuotes(int customerId)
{
string url = string.Format(#"https://my.server.dns/quotations/customer/{0}", customerId);
var task = GetJsonString(url);
// task.Wait();
await task;
customerQuotations = (IEnumerable<Quotation>)JsonConvert.DeserializeObject<Quotation>(task.Result);
}
and of course
var task = GetJsonString(url);
await task;
... (task.Result)
can (should) become
string result = await GetJsonString(url);
... (result)
And when you don't need the response object (for status code etc) then all this can be done in 1 line:
customerQuotations = await myClient.GetFromJsonAsync<Quotation[]>(url);
It looks like you are overcomplicating the async coding in the API call. Why do you need to construct a TaskCompletionSource? You may have reasons, but they are not evident in the code in your question.
Why not something like this:
public async Task CustomerRowSelectHandler(...)
{
await GetCustomerQuotes(...);
}
private async ValueTask GetCustomerQuotes(...)
{
string url = string.Format(#"....");
var http = new HttpClient(...);
HttpResponseMessage response = await http.GetAsync(url);
if (response.IsSuccessStatusCode)
customerQuotations = await response.Content.ReadFromJsonAsync<IEnumerable<Quotation>>() ?? Enumerable.Empty<Quotation>(); ;
// handle errors
}
Or even this, but you loose the error trapping.
customerQuotations = await http.GetFromJsonAsync<IEnumerable<Quotation>>(url);
You should also consider using the IHttpClientFactory to manage http instances.
So I'm prototyping some Azure Durable Functions, to try and understand to see if they will fit within a proposed solution for our internal API system.
Based on examples, I've created a Orchestrator Client (HelloOrchestratorClient.cs), that responds to a HttpTrigger. This client extracts some information from the original request, then proceeds to fire off a Orchestrator Function (HelloOrchestrator.cs) passing in some of the information extracted:
Complex HelloOrchestratorClient.cs:
[FunctionName("HttpSyncStart")]
public static async Task<HttpResponseMessage> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, methods: "get", Route = "orchestrators/{functionName}/wait")]
HttpRequestMessage req,
[OrchestrationClient] DurableOrchestrationClient starter,
string functionName,
ILogger log)
{
HttpReq originalRequest = new HttpReq() {
DeveloperId = GetDevKey(req,apiHeaderKey),
QueryString = req.RequestUri.Query,
APIName = GetQueryStringValue(req,APIName),
APIVersion = GetQueryStringValue(req,APIVersion)
};
string instanceId = await starter.StartNewAsync(functionName, originalRequest);
TimeSpan timeout = GetTimeSpan(req, Timeout) ?? TimeSpan.FromSeconds(30);
TimeSpan retryInterval = GetTimeSpan(req, RetryInterval) ?? TimeSpan.FromSeconds(1);
return await starter.WaitForCompletionOrCreateCheckStatusResponseAsync(
req,
instanceId,
timeout,
retryInterval);
}
The HelloOrchestrator.cs simply for now is just calling off to one of our internal API's and returning a JsonProduct payload (Simple POCO describing, you guessed it, a title), using a ActivityTigger named HelloOrchestrator.APICall to make the API call itself.
Complex HelloOrchestrator.cs:
[FunctionName("E1_JsonProduct")]
public static async Task<List<JsonProduct>> Run(
[OrchestrationTrigger] DurableOrchestrationContextBase context,
ILogger log)
{
List<JsonProduct> output = new List<JsonProduct>();
HttpReq r = context.GetInput<HttpReq>();
if(r != null)
{
if(r.DeveloperId == null)
{
return output;
}
output.Add(await context.CallActivityAsync<JsonProduct>("E1_CallAPI",r));
return output;
}
return output;
}
[FunctionName("E1_CallAPI")]
public async static Task<JsonProduct> APICall([ActivityTrigger] HttpReq req,
ILogger log)
{
JsonProduct products = null;
string u = $"{baseAddress}{req.APIVersion}/{req.APIName}{req.QueryString}";
var request = new HttpRequestMessage(HttpMethod.Get, u);
request.Headers.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json")
);
request.Headers.Add("x-apikey",req.DeveloperId);
log.LogInformation($"URL calling = '{request.RequestUri.AbsoluteUri}'.");
HttpResponseMessage response = await client.SendAsync(request);
// return await response.Content.ReadAsStringAsync();
if(response.IsSuccessStatusCode)
{
var formatter = new JsonMediaTypeFormatter
{
SerializerSettings = HelloProj.CosmosDB.Models.Products.Converter.Settings
};
products = await response.Content.ReadAsAsync<JsonProduct>(new [] {formatter});
}
return products;
}
Side Note: The plan is if I can get this to work, is to fan out a bunch of processes to different API's and fan back in again and merge the JSON payload and return it back to the originator.
Issue I'm experiencing
So, when my List<JsonProduct> is returned back from HelloOrchestrator.Run, I receive the following NullReferenceException found on this Gist (Big stack trace) and I receive a 500 response from the Orchestrator Client.
The following proves the output returned does actually have an object at runtime:
Could it be due to the complexity of JsonProduct (Again find the model classes here)? I ask, because when I swap out my Orchestrator Function for a simpler model structure, I don't receive a 500, I receive my JSON Payload.
This example shows the Simple Orchestrator Function HelloOrchestrator.cs, returning a simple TestToDo.cs (Gist for model) flat object that doesn't error:
Simple HelloOrchestrator.cs:
[FunctionName("E1_Todo")]
public static async Task<TestToDo> RunToDo(
[OrchestrationTrigger] DurableOrchestrationContextBase context,
ILogger log)
{
HttpReq r = context.GetInput<HttpReq>();
TestToDo todo = new TestToDo();
if(r != null)
{
todo = await context.CallActivityAsync<TestToDo>("E1_CallAPITodo",r);
}
return todo;
}
[FunctionName("E1_CallAPITodo")]
public async static Task<TestToDo> APITodoCall([ActivityTrigger] HttpReq req,
ILogger log)
{
var request = new HttpRequestMessage(HttpMethod.Get, "https://jsonplaceholder.typicode.com/todos/1");
request.Headers.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json")
);
log.LogInformation($"URL calling = '{request.RequestUri.AbsoluteUri}'. for {req.QueryString}");
HttpResponseMessage response = await client.SendAsync(request);
return await response.Content.ReadAsAsync<TestToDo>();
}
More Information
If you require my full prototype projects, you can find them here:
Complex Project (Throws 500 and exception)
When you run it, use the following in something like Postman (After F5ing it):
http://localhost:7071/api/orchestrators/E1_JsonProduct/wait?timeout=20&retryInterval=0.25&api=products&apiVersion=v1&filterByImprints=W%26N&N
Simple Project (No 500 or Exception thrown)
When you run it, use the following in something like Postman (after F5ing it):
http://localhost:7071/api/orchestrators/E1_Todo/wait?timeout=20&retryInterval=0.25
Looking at the callstack you posted, the NullReferenceException appears to be a bug in the DurableOrchestrationClient class. Looking at the code (which you can find here) is seems possible that if the query string you're using cannot be parsed correctly, a null-ref is possible.
You mentioned you're using the following URL for testing:
http://localhost:7071/api/orchestrators/E1_JsonProduct/wait?timeout=20&retryInterval=0.25&api=products&apiVersion=v1&filterByImprints=W%26N&N
I wonder if the last two characters (&N) are the source of the problem. Is is possible to encode the & or remove it entirely to isolate the problem?
Either way, it would be great if you could log an issue here: https://github.com/Azure/azure-functions-durable-extension/issues
In MessagesController.cs, following code is executed in Post method,
if (activity.Text.ToLowerInvariant().StartsWith("code:"))
{
var stateClient = activity.GetStateClient();
var botData = await stateClient.BotState.GetUserDataAsync(activity.ChannelId, activity.From.Id);
var token = botData.GetProperty<string>("AccessToken");
BotUserModel botUser = CreateNewUser(token);
var privateData = await stateClient.BotState.GetPrivateConversationDataAsync(activity.ChannelId, activity.Conversation.Id, activity.From.Id);
privateData.SetProperty<BotUserModel>("botUser", botUser);
}
else
{
await Conversation.SendAsync(activity, () => new LuisDialog());
}
This is saving botUser into PrivateConversationData dictionary
Inside the LUIS Dialog,
[LuisIntent("DoSomething")]
public async Task DoSomething(IDialogContext context, LuisResult result)
{
BotUserModel botUser;
context.PrivateConversationData.TryGetValue<BotUserModel>("botUser", out botUser);
// Just to test
context.PrivateConversationData.SetValue<BotUserModel>("TestValue", new BotUserModel());
}
Here, I'm getting an exception KeyNotFoundException:botUser
BotUserModel is marked [Serializable] and has few public properties - all with get/set. I checked the IBotBag (i.e. PrivateConversationData) and its empty
[LuisIntent("DoSomethingNew")]
public async Task DoSomethingNew(IDialogContext context, LuisResult result)
{
// Assuming DoSomething intent is invoked first
BotUserModel botUser;
context.PrivateConversationData.TryGetValue<BotUserModel>("TestValue", out botUser);
// Here, no exception!
}
Now, here I get the value of TestValue set in LUIS Dialog in DoSomething method.
So essentially, any data set to PrivateConversationData or UserData inside LUIS Intent is accessible by other LUIS intents; whereas, data set in MessageController.cs (before LUIS is called) is not accessible within LUIS.
Tried with UserData as well.
Am I missing anything?
You are forgetting to set the private data store back into the state client. This should make it work.
var privateData = await stateClient.BotState.GetPrivateConversationDataAsync(activity.ChannelId, activity.Conversation.Id, activity.From.Id);
privateData.SetProperty<BotUserModel>("botUser", botUser);
await stateClient.BotState.SetPrivateConversationDataAsync(activity.ChannelId, activity.Conversation.Id, activity.From.Id, privateData);
Check out the documentation on the state client.
I am trying to communicate with echo service on a server using web sockets in my bot. I am using WebSocketSharp assembly to create web socket connection. I want to echo back whatever user types in the bot but, it never fires "ws.OnMessage" event and I get back no response. I tested the connection on the console application and every thing works fine there. Please suggest what I am doing wrong here.
Following is my MessageController
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
await Conversation.SendAsync(activity, () => new HumanCollaboratorDialog());
}
else
{
HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
Following is my HumanCollaboratorDialog class
[Serializable]
public class HumanCollaboratorDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(this.MessageReceivedAsync);
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var message = await result;
using (var ws = new WebSocket("ws://Some IP addrress:8080/human-collaborator/echo"))
{
ws.OnMessage += async (sender, e) =>
{
try
{
await context.PostAsync(e.Data);
}
catch (Exception ex)
{
await context.PostAsync($"Exception: {ex.Message}");
}
};
ws.ConnectAsync();
var msg = message.Text;
ws.Send(msg);
}
context.Wait(this.MessageReceivedAsync);
}
}
The "MessageReceivedAsync" is not the correct place to create a websocket. WebSockets in the bot framework are used for receiving messages in a Direct Line connection scenario. A StreamUrl obtained from a call to StartConversationAsync is used to create the web socket:
var token = await new DirectLineClient(dlSecret).Tokens.GenerateTokenForNewConversationAsync();
// Use token to create conversation
var directLineClient = new DirectLineClient(tokenResponse.Token);
var conversation = await directLineClient.Conversations.StartConversationAsync();
using (var webSocketClient = new WebSocket(conversation.StreamUrl))
{
webSocketClient.OnMessage += WebSocketClient_OnMessage;
webSocketClient.Connect();
etc.
Please see here: https://github.com/Microsoft/BotBuilder-Samples/blob/master/CSharp/core-DirectLineWebSockets/DirectLineClient/Program.cs
If I have a standalone process that runs continuously, is there a good method of adding an asp.net websocket interface to that such that I can send messages to interact with this process and also broadcast status updates? Messages can be quite large and/or frequent.
This process doesn't need to be scheduled as it will be running as soon as the application starts.
I am asking because initially I thought I would just run an asp.net application that creates a thread on startup (in Startup.Configure I think?) but I've been reading that asp.net isn't designed for having background threads. Using Windows Services is brought up but I am developing for Linux and OSX.
This is sort of what I'm trying to achieve:
public class Startup
{
BackgroundServer server;
public void Configure(IApplicationBuilder app)
{
// start server in another thread
Task task = new Task( () => server.run(); );
task.Start();
app.Map("/ws", webSocketsApp =>
{
// need this to enable websocket middleware!
webSocketsApp.UseWebSockets(new WebSocketOptions() {ReplaceFeature = true, });
webSocketsApp.Use(async (context, next) =>
{
if (context.WebSockets.IsWebSocketRequest)
{
var webSocket = await context.WebSockets.AcceptWebSocketAsync();
await Task.WhenAll([]{ProcessMessages(webSocket), ProcessSends(websocket)});
return;
}
await next();
});
});
}
private async Task ProcessMessages(WebSocket webSocket)
{
byte[] buffer = new byte[1024 * 4];
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
while (!result.CloseStatus.HasValue)
{
await server.sendMessage(websocket, message);
}
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
}
private async Task ProcessSends(WebSocket webSocket)
{
while (!webSocket.isStateTerminal)
{
var message = await server.getMessage();
await webSocket.SendAsync(new ArraySegment<byte>(message), Text, true, CancellationToken.None);
}
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
}
}