I'm trying to create a Azure function with a Cosmos Trigger (in c#) that monitors changes to a CosmosDB, this seems relatively simple and I have managed to do this without trouble, I have an Azure function that logs changes to the DB to the log console. I am trying to write an output binding to send the changes to Azure SignalR but when I try this, I am met with isolated process doesn't support the assemblies required to do this. Does anyone have a very simple example of a c# Azure Cosmos Trigger function that sends the changes detected to a Cosmos DB to an Azure SignalR servicee so I can subscribe to this and report these to a client app. Any help would be greatly appreciated.
The code I found on the web for what I want to do (this is just a test function) is below:
[Function("CosmosTriggerSigR")]
public void Run([CosmosDBTrigger(
databaseName: "test",
collectionName: "testCollection",
ConnectionStringSetting = "cosmos_DOCUMENTDB",
LeaseCollectionName = "leases")]
IReadOnlyList<MyDocument> input,
[SignalR(HubName = "events", ConnectionStringSetting = "SignalRConnectionString")]
IAsyncCollector<SignalRMessage> signalRMessages,
ILogger log)
{
if (input != null && input.Count > 0)
{
_logger.LogInformation("Documents modified: " + input.Count);
_logger.LogInformation("First document Id: " + input[0].Id);
}
}
and the when trying to deploy it, it shows this error:
C:\Users\CosmosSigr1204\CosmosTriggerSigR.cs(29,14): error AZFW0001: The attribute 'SignalRAttribute' is a WebJobs attribute and not supported in the .NET Worker (Isolated Process). [C:\Users\CosmosSigr1204\CosmosSigr1204.csproj]
The terminal process "C:\Program Files\dotnet\dotnet.exe 'publish', '--configuration', 'Release', '/property:GenerateFullPaths=true', '/consoleloggerparameters:NoSummary'" terminated with exit code: 1.
I know pretty much nothing about Azure SignalR and I'm trying to muddle through so apologies if the code above isn't what it should be for what I'm trying to do.
Here is a full solution that uses the combination of services you specify: https://github.com/ealsur/serverlessazurefriday
Particularly this Function: https://github.com/ealsur/serverlessazurefriday/blob/master/src/DistributedDashboard/NotificationsTrigger.cs which ties the CosmosDBTrigger and SignalR output like so:
[FunctionName("Notifications")]
public static async Task Run(
[CosmosDBTrigger(
databaseName: "eventsDb",
collectionName: "events",
LeaseCollectionPrefix = "Notifications",
ConnectionStringSetting = "CosmosDBAzureFriday",
PreferredLocations = "%REGION%",
LeaseCollectionName = "leases")]
IReadOnlyList<Document> events,
[SignalR(HubName = "events", ConnectionStringSetting = "SignalRAzureFriday")]
IAsyncCollector<SignalRMessage> signalRMessages,
ILogger log)
{
await signalRMessages.AddAsync(new SignalRMessage()
{
Target = "console",
Arguments = new[] {
events.Select(singleEvent => JsonConvert.DeserializeObject<ConsoleLog>(singleEvent.ToString()))
}
});
}
In this case, the code will send 1 SignalR message containing all the documents that were received on the Trigger, you could opt to send 1 SignalR message per Trigger document, that is up to your application design.
In the case of this solution, the client application (browser) connects to the SignalR hub using the JS library and receives the SignalR message that contain all the Cosmos DB documents and consumes the array (reference https://github.com/ealsur/serverlessazurefriday/blob/master/src/ClientApp/scripts/site.js):
// Assuming connection is a SignalR connection created by SignalR library
// https://learn.microsoft.com/aspnet/core/signalr/javascript-client?#connect-to-a-hub
connection.on('console', function(messages){
angular.forEach(messages, function(message){
term.writeln('>> Device '+message.deviceId +' in ' + message.region + ' reports value of ' + message.value);
});
});
Where console is just the name that matches the Target on the SignalR output message.
Related
C# learner here. I hope the question makes sense, but if not, read on!
I have an existing Azure function setup (.NET 6) that, when it receives a http trigger, will trigger an orchestrator function that uses an activity trigger to start a function that will copy all messages from an Azure Storage Queue to A Cosmos DB (the QueueStore function below). I would like to also send each of the messages to a client using SignalR via an existing SignalR Service and Hub, also in Azure.
There is a lot of documentation on creating the SignalR and negotiate functions but how do I send a message from within my already called function?
The code for the copy function is below. I'm sure those more experienced developers will spot lots of ways I can optimise things, but I am honestly just happy getting it to work at this stage. As it currently stands, the function works as expected and required but I want to add the SignalR functionality at the commented location in the code.
How can I best go about this?
[FunctionName(nameof(QueueStore))]
public static async Task<string> QueueStore([ActivityTrigger] QueueName queue, ILogger log)
{
// Get the connection string
string connectionString = Environment.GetEnvironmentVariable("QueueStorage");
try
{
CosmosClient client = new CosmosClient("some info here");
Database database = client.GetDatabase("database");
bool databaseExists = true;
try
{
var response = await database.ReadAsync();
}
catch (CosmosException ex)
{
if (ex.StatusCode.Equals(HttpStatusCode.NotFound))
{
// Does not exist
databaseExists = false;
}
}
//Instantiate a QueueClient which will be used to manipulate the queue
QueueClient queueClient = new QueueClient(connectionString, queue.Name);
QueueProperties properties = await queueClient.GetPropertiesAsync();
bool appDisconnected = false;
//string message = "Stored messages ";
if (queueClient.Exists() && databaseExists)
{
Container container = await database.CreateContainerIfNotExistsAsync(id: queue.Name,
partitionKeyPath: "/partKey", //name of the json var we want as partition key
throughput: 400
);
while (appDisconnected == false)
{
if (queueClient.GetProperties().Value.ApproximateMessagesCount == 0)
{
Thread.Sleep(100);
}
else
{
QueueMessage[] retrievedMessage = await queueClient.ReceiveMessagesAsync(1);
var fd = JsonConvert.DeserializeObject<JObject(retrievedMessage[0].Body.ToString());
if (!fd.ContainsKey("disconnected"))
{
PartitionKey partKey = new PartitionKey(queue.PartKey);
// save to db
var createdItem = await container.CreateItemAsync<JObject>(
item: fd,
partitionKey: partKey);
//######## HERE IS WHERE I WANT TO SEND THE fd Object via SignalR
//######## I have tried many different things but nothing works
await queueClient.DeleteMessageAsync(retrievedMessage[0].MessageId,
retrievedMessage[0].PopReceipt);
}
else
{
appDisconnected = true;
}
}
}
return "Copied all Items";
}
else
{
return $"The queue peek didn't work because I can't find the queue:-(";
}
}
catch (Exception ex)
{
return ex.Message;
}
}
I have tried calling a SignalR function from the orchestrator but that is adding a function outside the copy function process and would mean duplicating the calls to the queue and doesn't really help. I haven't seen any way to just send a SignalR message from the location indicated in the code. I have also tried standard .Net SignalR code, but can find no examples that have worked for me. Any pointers and suggestions would be greatly received.
Can this be done? Should I just create an entirely new function app and make a http call to that?
Trying to make my intended version of this has been causing me a lot of issues and, having not found any documentation on it, I may be architecting things wrong but thought I would ask here for any suggestions before re-writing everything as I am rather time-limited to work on this app.
Thanks in advance for any help!
I have tried adding an example Azure SignalR Function called by the orchestrator and also called from the commented area of the code and I have tried examples from the .NET documentation and the SignalR Azure Functions examples. I was hoping that there were examples of this or a tutorial somewhere I could follow but it seems like I'm trying to do something no one else has done, which might mean I'm barking up the wrong tree entirely... :-(
I need to query CustomEvents under application insights in an azure function.
I was able to read CustomEvents using below package:
Microsoft.Azure.ApplicationInsights.Query
Here is the code:
string applicationId = "xxxx-xxxx-xxxx";
string key = "xxxxxxxxxxx";
// Create client
var credentials = new ApiKeyClientCredentials(key);
var applicationInsightsClient = new ApplicationInsightsDataClient(credentials);
// Query Application Insights
var query = "customEvents" +
" | where timestamp > ago(840h)" +
" | take 3";
var response = await applicationInsightsClient.Query.ExecuteWithHttpMessagesAsync(applicationId, query);
The library 'Microsoft.Azure.ApplicationInsights.Query is however deprecated and suggestion is to use Azure.Monitor.Query
Below is the code that Microsoft documentation has as an example to query logs using Azure.Monitor.Query :
Azure.Response<Azure.Monitor.Query.Models.LogsQueryResult> response = await logsQueryClient.QueryWorkspaceAsync(
"<workspaceId>",
"customEvents ",
new QueryTimeRange(TimeSpan.FromMinutes(300)));
Since this library queries using workspace id, I linked my application insights instance to a log analytics workspace instance. However the function fails with a BadArgumentError "Failed to resolve table or column expression named 'customEvents'"
Is there a way we can query CustomEvents using the package Azure.Monitor.Query?
Any help is appreciated.
Thanks
Yes, it works.
Below is a tested code.
Once you link your Application Insights to Azure Monitor workspace, you can query your AI tables from that WS, without the need to use app().
The thing is that the tables` names are different, e.g., traces becomes AppTraces.
In the same manner, customEvents becomes AppEvents.
Well, it turns out it is even documented, under Migrate to workspace-based Application Insights resources
Legacy table name
New table name
Description
availabilityResults
AppAvailabilityResults
Summary data from availability tests.
browserTimings
AppBrowserTimings
Data about client performance, such as the time taken to process the incoming data.
dependencies
AppDependencies
Calls from the application to other components (including external components) recorded via TrackDependency() – for example, calls to REST API, database or a file system.
customEvents
AppEvents
Custom events created by your application.
customMetrics
AppMetrics
Custom metrics created by your application.
pageViews
AppPageViews
Data about each website view with browser information.
performanceCounters
AppPerformanceCounters
Performance measurements from the compute resources supporting the application, for example, Windows performance counters.
requests
AppRequests
Requests received by your application. For example, a separate request record is logged for each HTTP request that your web app receives.
exceptions
AppExceptions
Exceptions thrown by the application runtime, captures both server side and client-side (browsers) exceptions.
traces
AppTraces
Detailed logs (traces) emitted through application code/logging frameworks recorded via TrackTrace().
using Azure;
using Azure.Identity;
using Azure.Monitor.Query;
using Azure.Monitor.Query.Models;
string workspaceId = "...";
var client = new LogsQueryClient(new DefaultAzureCredential());
try
{
Response<LogsQueryResult> response = await client.QueryWorkspaceAsync(
workspaceId,
"AppEvents | count",
QueryTimeRange.All);
LogsTable table = response.Value.Table;
foreach (var row in table.Rows)
{
Console.WriteLine(row);
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
I have created an Azure SignalR Serverless service with Azure Functions.
My client is a .NET 6 WPF Application.
The negotiate function is working as expected, and the connection gets established succesfully.
The CosmosDBTrigger, HttpTrigger and TimerTrigger functions also work as expected.
However, the SignalRTrigger isn't working and I can't figure out why.
SignalRTrigger function:
[FunctionName("SignalRTest")]
public async Task SignalRTest([SignalRTrigger("myHub", "messages", "SignalRTest")] InvocationContext invocationContext, string message, ILogger logger)
{
logger.LogInformation($"Receive {message} from {invocationContext.ConnectionId}.");
await Clients.All.SendAsync("signalRTestMessage", message);
}
Client Configuration:
connection = new HubConnectionBuilder()
.WithUrl("https://<SiteURL>.azurewebsites.net/api")
.Build();
await connection.StartAsync().ContinueWith(async (e) =>
{
try
{
await connection.InvokeAsync("SignalRTest", "TestMessage");
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
});
The exception always returns the error message:
Invocation failed, status code 404
I have configured the SignalR Upstream with the signalr_extension key generated in Azure Functions.
I have followed the official documentation on Microsoft docs but still couldn't fix the issue.
I empathize with you since we've been struggling with sending messages as well even though so much documentation makes it appear trivial. One thought I had on your situation is: Should you use the URL from the negotiate response when creating a hubConnection?
Here's our typescript example that uses a NegotiateResponse object with the URL and AccessToken returned from the negotiate HTTP call:
this.signalRService.negotiate().subscribe({
next: (negotiateResponse) => {
let options = {
accessTokenFactory: () => negotiateResponse.accessToken,
};
const connection = new signalR.HubConnectionBuilder()
.withUrl(negotiateResponse.url, options)
.build();
P.S. As mentioned we are also struggling with message sending, so this may not be a solution for you although I hope it is.
I've created negotiate and send message functions in Azure Functions (similar to the samples below) to incorporate the SignalR Service. I'm setting UserId on the SignalRMessage by using a custom authentication mechanism.
https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-signalr-service?tabs=csharp
[FunctionName("negotiate")]
public static SignalRConnectionInfo Negotiate(
[HttpTrigger(AuthorizationLevel.Anonymous)]HttpRequest req,
[SignalRConnectionInfo
(HubName = "chat", UserId = "{headers.x-ms-client-principal-id}")]
SignalRConnectionInfo connectionInfo)
{
// connectionInfo contains an access key token with a name identifier claim set to the authenticated user
return connectionInfo;
}
[FunctionName("SendMessage")]
public static Task SendMessage(
[HttpTrigger(AuthorizationLevel.Anonymous, "post")]object message,
[SignalR(HubName = "chat")]IAsyncCollector<SignalRMessage> signalRMessages)
{
return signalRMessages.AddAsync(
new SignalRMessage
{
// the message will only be sent to this user ID
UserId = "userId1",
Target = "newMessage",
Arguments = new [] { message }
});
}
I'd like to send a push notification if the client is no longer connected instead of adding a new object to the IAsyncCollector. I've also set up AppCenter push framework properly, but I'm facing an issue. Is there an easy way to find out which UserId is still connected to the hub? This way, I could decide to send a push. What is the recommended Microsoft guidance on this issue?
Have a look at this feature: Azure SignalR Service introduces Event Grid integration feature, where the SignalR Service emits two following event types:
Microsoft.SignalRService.ClientConnectionConnected
Microsoft.SignalRService.ClientConnectionDisconnected
More details here.
I am working to make an internal application available from an external location. To accomplish this I’m looking into different options like the use of a rest API or using a socket server which can be placed between the old application and the database on the server. One of the concerns of the end product is that if multiple users alter the same data and sent this to the server that there is the possibility that they overwrite each other’s changes. The planned approach for this is have a server sided log for data changes, and notify the user that relevant data has been altered to give them the choice to reload their form. The latter part is the issue from which my question has come forth. Unlike sockets web protocols do not work bidirectional. Potential fixes that I have found are polling, framing and WebHook of which the latter seem to be the most efficient.
However looking at the different available WebHook receiver samples they seem to have something in common. They use either MVC or WebAPI and all have an altered WebConfig in which the method config.InitializeCustomWebHooks(); has been added.
I’ve been trying to find a way to add this in by altering the Program.cs without success.
Also I’ve tried to find a method that has to do with Hooks using the HttpClient (which I’ve used for the rest of my demo application so far), WebClient and HttpResponse. However this search also did not bear any fruits. Also the only post of someone who tried to accomplish something similar (have a windows forms application listen to GitHub) that I found got a [response][1] that made me believe that webhooks and windows forms just don’t go together.
This search has ended with me wondering or:
Is it a possibility to consume an API 2 services WebHooks with a windows form application?
And if possible I would like to be informed how to do this?
For now my API 2 server has been pretty much altered according the example provided by [Asp.Net][2]
And my client has implemented the CustomWebHookHandler from the CustomReceiver from the same.
Edit for next issue:
note: both client and server contain the same packages as used in the repo as shown by Peter Bons.
The client contains a secret key within the app.config, matching the key within the subscribe method and the new Startup.cs has been called (I am able to sent echo's to the client sided hosted server) so that part is working. The registration class also matches up with the example repo
Client initiates the webhook with the following method:
public void SubscribeTo(String subscribeString)
{
HttpResponseMessage result;
// Create a webhook registration
Registration registration = new Registration
{
WebHookUri = $"{adressReceiver}/api/webhooks/incoming/custom",
Description = "A message is posted.",
Secret = "12345678901234567890123456789012",
Filters = new List<string> { "EmployeeChanged" }
};
result = client.PostAsJsonAsync(#"webhooks/registrations", registration).Result;
string resultString = result.ToString();
}
Where adress receiver is the same string that was used to start the echoable webservice.
Upon debugging the method the following actions server/client occur:
on the line containing result = the server sided debug point in CustomFilterProvider.GetFiltersAsync get's triggered.
At the return method it shows that 1 filer was returned with the Name: "EmployeeChanged" back on the client the resultString contains the StatusCode: Created
During the method the client has the system output:
ClientApp.exe Information: 0 : Registered 'IWebHookReceiver' instances with the following names: custom.
ClientApp.exe Information: 0 : Processing incoming WebHook request with receiver 'custom' and id ''.
ClientApp.exe Information: 0 : Registered configuration setting 'Custom' for ID '''.
And the server:
Application Insights Telemetry (unconfigured):
{
"name": "Microsoft.ApplicationInsights.Dev.RemoteDependency",
"time": "2017-09-12T10:16:25.8947601Z",
"tags": {
"ai.internal.nodeName": "PC-78.vericon.nl",
"ai.operation.id": "1348c1c9-4b7a5ac6dd5f1940",
"ai.cloud.roleInstance": "PC-78.vericon.nl",
"ai.internal.sdkVersion": "rdddsd:2.4.1-1362"
},
"data": {
"baseType": "RemoteDependencyData",
"baseData": {
"ver": 2,
"name": "GET /api/webhooks/incoming/custom",
"id": "|1348c1c9-4b7a5ac6dd5f1940.",
"data":
"http://localhost:55999/api/webhooks/incoming/custom?echo=e9f7b03ca8aa4226a7a9dfc99dacff16",
"duration": "00:00:00.2046329",
"resultCode": "200",
"success": true,
"type": "Http",
"target": "localhost:55999",
"properties": { "DeveloperMode": "true" }
}
}
}
Application Insights Telemetry (unconfigured):
{
"name": "Microsoft.ApplicationInsights.Dev.Request",
"time": "2017-09-12T10:14:50.0204275Z",
"tags": {
"ai.internal.nodeName": "PC-78.vericon.nl",
"ai.operation.name": "POST /api/webhooks/registrations",
"ai.operation.id": "lXKbEdh4GOU=",
"ai.location.ip": "::1",
"ai.cloud.roleInstance": "PC-78.vericon.nl",
"ai.internal.sdkVersion": "web:2.4.1-1362"
},
"data": {
"baseType": "RequestData",
"baseData": {
"ver": 2,
"id": "|lXKbEdh4GOU=.1348c1c8_",
"name": "POST /api/webhooks/registrations",
"duration": "00:01:36.1228603",
"success": true,
"responseCode": "201",
"url": "http://localhost:55997/api/webhooks/registrations",
"properties": { "DeveloperMode": "true" }
}
}
}
After a bit of research my reasoning for this output is that the server sends an echo request to the ip given by the client to see or it is a reachable destination. The code created also implies that the server found this adress because otherwise a code BadRequest would occur
So I think that the webhook get's correctly established futher on in the application I have got a method that makes a post request to the server. I put a debug point in the server sided method to see what happens (there's also is a debug point in my client handler).
after doing the client sided action that should get me in my controller the debug point within indeed get's triggered in my method:
public async Task<IHttpActionResult> Post(Employee e)
{
await this.NotifyAsync(CustomFilterProvider.EmployeeChanged, new { Employee = e });
if (EmployeeModifier.updateEmployee(e))
{
log.Info("User: " + User.Identity.Name + " Updated an employee with the following new value " + Newtonsoft.Json.JsonConvert.SerializeObject(e));
return Ok();
}
else
{
log.Error("User: " + User.Identity.Name + " failed to update an employee");
return BadRequest();
}
}
when stepping through it it follows the route towards to return Ok()
the client receives the system output:
ClientApp.exe Information: 0 : Processing incoming WebHook request with receiver 'custom' and id ''.
However the debug point within the clients handler never get's triggered.
the handler for now looks like:
class CustomWebHookHandler : WebHookHandler
{
public CustomWebHookHandler()
{
}
public override Task ExecuteAsync(string generator, WebHookHandlerContext context)
{
System.Windows.Forms.MessageBox.Show("In handler");
return Task.FromResult(true);
}
}
My client startup.cs Configuration method looks like:
public void Configuration(IAppBuilder appBuilder)
{
var config = new HttpConfiguration();
var controllerType = typeof(WebHookReceiversController);
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
"DefaultApi",
"api/{controller}/{id}",
new { id = RouteParameter.Optional }
);
config.InitializeReceiveCustomWebHooks();
var traceWriter = config.EnableSystemDiagnosticsTracing();
traceWriter.IsVerbose = true;
traceWriter.MinimumLevel = TraceLevel.Error;
appBuilder.UseWebApi(config);
}
So that contains the var controllerType
I hope this is enough relevant code for you, my fear is that my server is only sending echo's/ping requests to my client.
But I don't know how to change that since I've tried both NotifyAsync and NotifyAllAsync, with and without Filter( in client Registration class instance). The clients OWIN hosts keeps up for sure since it is echo able and the client output shows that it actually does receive a message from the server but that's where I'm crashed.