Send Azure SignalR message from Azure Function with CosmosDB Trigger - c#

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.

Related

azure functions server (isolated-process): Invoking SignalR group functions from client

I am trying to add a SignalR client to specific SignalR group on an azure functions server (isolated-process). The server side code that I am trying to invoke is the following:
[Function("SendToGroup")]
[SignalROutput(HubName = "serverless", ConnectionStringSetting = "AzureSignalRConnectionString")]
public static SignalRMessageAction SendToGroup([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req)
{
using var bodyReader = new StreamReader(req.Body);
return new SignalRMessageAction("newMessage")
{
Arguments = new[] { bodyReader.ReadToEnd() },
GroupName = "groupToSend"
};
}
I have tried invoking the above from the client side in C# with the following code to no avail:
HubConnection _connection = new HubConnectionBuilder().WithUrl("http://localhost:7071/api").Build();
Dispatcher.Dispatch(async () => await _connection.StartAsync());
// This does not work
await HubConnectionExtensions.InvokeAsync(_connection, "SendToGroup");
In my searches, I have not found a single C# client code sample that shows how to connect to and remove oneself from signalR hub groups. I would really appreciate being pointed in the right direction.

Consuming SignalR services in c# serverless inside the code

Ivé this code...
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "v1/Sala")]
HttpRequest req, [SignalR(HubName = "{query.HubName}")]
IAsyncCollector<SignalRMessage> signalRMessages) {
await signalRMessages.AddAsync(
new SignalRMessage {
Target = "newMessage",
Arguments = new[] { "Hello" }
});
}
And this code works good and I am happy. But I've a need, in partiular on my EventGridTrigger...
As you could noticed on the code above, the hubname is dinamic and an EventGridTrigger is a sort of special kind of endpoint (Your client app will not call and consume it...SignalR will instead).
But I am capable to identify the hubname on my EventGridTrigger...I can do this:
SignalRDataEvent data =
JsonConvert.DeserializeObject<SignalRDataEvent(eventGridEvent.Data.ToString());
string hubname = data.hubname;
But now...I need to send a signalR message using my variable hubname. Since I can't put [SignalR(HubName = "{query.HubName}")] IAsyncCollector signalRMessages) on my EventGridTrigger, I need to create the object SignalR, probably pass credentials, hubname, etc and then send a message. I can't find any sample for this - At least no samples that can work in serverless c# azure functions. Can someone help me wikth this ?
Try the following:
[SignalR(HubName = "{data.hubName}")]

Azure function is not logging custom events, dependencies to app insights

We have a Azure Function V3 developed in C#. We tried to log some custom events, dependencies, etc to Azure Application Insights. We are not able to log in app insights using TelemetryClient. The code runs fine without any errors. Also, could see instrumentation key retrieved from config file. However Ilogger logs can be found in app insights traces table. Please find below code that we used,
public class CommunityCreate
{
private readonly TelemetryClient telemetryClient;
public CommunityCreate(TelemetryConfiguration telemetryConfiguration)
{
this.telemetryClient = new TelemetryClient(telemetryConfiguration);
}
[FunctionName("Function1")]
[return: ServiceBus("sample", Connection = "ServiceBusProducerConnection")]
public async Task<string> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "Account/{id:int?}")] HttpRequest req, string id, ILogger log)
{
//log.LogInformation("C# HTTP trigger function processed a request.");
DateTime start = DateTime.UtcNow;
string name = req.Query["name"];
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
var evt = new EventTelemetry("Function called");
evt.Context.User.Id = name;
this.telemetryClient.TrackEvent(evt);
// Generate a custom metric, in this case let's use ContentLength.
this.telemetryClient.GetMetric("contentLength").TrackValue(req.ContentLength);
// Log a custom dependency in the dependencies table.
var dependency = new DependencyTelemetry
{
Name = "GET api/planets/1/",
Target = "swapi.co",
Data = "https://swapi.co/api/planets/1/",
Timestamp = start,
Duration = DateTime.UtcNow - start,
Success = true
};
dependency.Context.User.Id = name;
this.telemetryClient.TrackDependency(dependency);
telemetryClient.TrackEvent("Ack123 Recieved");
telemetryClient.TrackMetric("Test Metric", DateTime.Now.Millisecond);
return name;
}
}
Please make sure you're using the correct packages as below:
Microsoft.Azure.WebJobs.Logging.ApplicationInsights, version 3.0.18
and
update the package Microsoft.NET.Sdk.Functions the latest version 3.0.9.
If you're running the project locally, please add the APPINSIGHTS_INSTRUMENTATIONKEY in local.settings.json, like below:
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "xxxx",
"FUNCTIONS_WORKER_RUNTIME": "dotnet",
"APPINSIGHTS_INSTRUMENTATIONKEY": "xxx"
}
}
Or if you're running it on azure portal, please configure the Application insights with azure function.
Then I tested your code, the custom events or dependency are correctly logged into Application insights. Here is the screenshot:
If you still have the issue, please let me know(and please also provide more details).

Why isn't Azure SignalR sending the messages published by the Azure Functions?

I have deployed a set of Azure Functions (v2).
I want to have an iOS application being notified when something happens on the server side so I created an Azure SignalR Service, configured as Serverless.
I have created and deployed a new negotiate Azure Function, as explained in the documentation, that publishes subscription information on a service bus queue:
[FunctionName("negotiate")]
public async Task<SignalRConnectionInfo> Negotiate(
[HttpTrigger(AuthorizationLevel.Anonymous)]
HttpRequest httpRequest,
ClaimsPrincipal claimsPrincipal,
[SignalRConnectionInfo(HubName = "updates", UserId = "{headers.x-ms-client-principal-id}")]
SignalRConnectionInfo connectionInfo)
{
try
{
// Uses the name identifier for now.
Claim nameIdentifierClaim = claimsPrincipal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
string userId = nameIdentifierClaim.Value;
foreach (Guid groupId in claimsPrincipal.GetGroups())
{
var subscription = new SynchronizationSubscriptionContract {UserId = userId, GroupId = groupId};
await m_serviceBus.SendAsync(JsonConvert.SerializeObject(subscription));
}
return connectionInfo;
}
catch (Exception exc)
{
//
}
}
The Service Bus is bound to an Azure Function that adds the user to the list of groups:
[ServiceBusAccount(Constants.Configuration.ServiceBusConnectionString)]
[FunctionName("subscribe")]
public async Task AddToGroupAsync(
[ServiceBusTrigger("synchronization-subscriptions")] string requestMessage,
[SignalR(HubName = "updates")] IAsyncCollector<SignalRGroupAction> signalRGroupActions)
{
var synchronizationSubscriptionContract = JsonConvert.DeserializeObject<SynchronizationSubscriptionContract>(requestMessage);
string groupName = synchronizationSubscriptionContract.GroupId;
var addGroupAction = new SignalRGroupAction
{
UserId = synchronizationSubscriptionContract.UserId,
GroupName = groupName,
Action = GroupAction.Add
};
await signalRGroupActions.AddAsync(addGroupAction);
}
So far so good.
Whenever one of my existing Azure Functions needs to publish a message on the SignalR channel, it sends a message on a dedicated service bus queue that is bound to another Azure Function. The Azure Function then grabs the message and sends it to the Azure SignalR Service:
[ServiceBusAccount(Constants.Configuration.ServiceBusConnectionString)]
[FunctionName(Api.Functions.Synchronization.UpdateSynchronizationInfo)]
public async Task Run(
[ServiceBusTrigger("synchronization-requests")] string requestMessage,
[SignalR(HubName = "updates")]
IAsyncCollector<SignalRMessage> signalRMessages
)
{
var requestContract = JsonConvert.DeserializeObject<SynchronizationUpdateRequestContract>(requestMessage);
var request = m_mapper.Map<SynchronizationUpdateRequest>(requestContract);
// Do more stuff.
string groupName = request.GroupId;
var updateMessage = new SignalRMessage
{
GroupName = groupName,
Target = "notify",
Arguments = new string[] {}
};
await signalRMessages.AddAsync(updateMessage);
}
Based on the logs that I added at different places in the Azure Functions, I see no errors and everything seemed to be called properly. However, I am not seeing any messages on signalR: the message count stays at 0 even if the connection count increases. My application does not received any messages.
Question
Why aren't the messages being sent to the Azure SignalR? What am I missing?
Wow.
The message seems to be dropped if the Arguments property is an empty list.
When I changed to Message instance to the following, it immediately worked:
var updateMessage = new SignalRMessage
{
GroupName = groupName,
Target = "notify",
Arguments = new[] { "Lulz" }
};

DocumentDB with Azure Functions

I'm trying to connect an Azure DocumentDB and save documents using Azure Functions but I don't know how to create the connection.
You can do it using the Azure Portal.
After you created the DocumentDB -
Create new Azure Function.
Go to the Integrate Tab.
You can choose Azure Document DB as an output for your function.
Choose your Document DB/Database Name/Collection you want to use.
Document parameter name is the Output of your function.
For example
using System;
public static void Run(string input, out object document, TraceWriter log)
{
log.Info($"C# manually triggered function called with input: {input}");
document = new {
text = $"I'm running in a C# function! {input}"
};
}
you need to provide out object which is the same as you defined in the output tab.
You can just use the document client directly:
var endpoint = "https://XXXXX.documents.azure.com:443/";
var authKey = "XXXXX";
using (var client = new DocumentClient(new Uri(endpoint), authKey))
{
var sqlCountQuery = "select value count(1) from c";
IDocumentQuery<dynamic> query = client.CreateDocumentQuery<dynamic>(UriFactory.CreateDocumentCollectionUri("YOUR_DB_ID", "YOUR_COLLECTON_ID"), sqlCountQuery).AsDocumentQuery();
....
}
Azure Functions supports Document DB (Cosmos DB) out-of-the-box. You can just simply add an environment variable called AzureWebJobsDocumentDBConnectionString in V1 or AzureWebJobsCosmosDBConnectionString in V2.
Then just use a CosmosDBTrigger binding attribute for input binding like (in C# for example):
public static class UpsertProductCosmosDbTrigger
{
[FunctionName("ProductUpsertCosmosDbTrigger")]
public static void Run(
[CosmosDBTrigger(
// Those names come from the application settings.
// Those names can come with both preceding % and trailing %.
databaseName: "CosmosDbDdatabaseName",
collectionName: "CosmosDbCollectionName",
LeaseDatabaseName = "CosmosDbDdatabaseName",
LeaseCollectionName = "CosmosDbLeaseCollectionName")]
IReadOnlyList<Document> input,
TraceWriter log)
...
For output binding use DocumentDB output binding attribute in V1 and CosmosDB in V2 like:
[FunctionName("ProductUpsertHttpTrigger")]
public static async Task<HttpResponseMessage> Run(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "products")]
HttpRequestMessage req,
[DocumentDB(
databaseName: "%CosmosDbDdatabaseName%",
collectionName: "%CosmosDbCollectionName%")] IAsyncCollector<Product> collector,
TraceWriter log)
...
I've written a blog post about this: https://blog.mexia.com.au/cosmos-db-in-azure-functions-v1-and-v2
var EndpointUrl = "EndpointUrl";
var PrimaryKey = "PrimaryKeyValue"
this.client = new DocumentClient(new Uri(EndpointUrl), PrimaryKey);
Database database = await this.client.CreateDatabaseIfNotExistsAsync(new Database { Id = cosmoDbName });
you can get the End-point-URL and Primary-Key value from the azure portal in the keys section.
Assume C# has similar SDK like Java. The below is for Java
There are two ways you can connect to documentDB from an Azure function.
Using SDK
DocumentClient documentClient = new DocumentClient(
"SERVICE_ENDPOINT",
"MASTER_KEY",
ConnectionPolicy.GetDefault(),
ConsistencyLevel.Session);
Refer - [https://learn.microsoft.com/en-us/azure/cosmos-db/sql-api-java-samples][1]. This has .Net Samples too.
Binding
#FunctionName("CosmosDBStore")
#CosmosDBOutput(name = "database",
databaseName = "db_name",
collectionName = "col_name",
connectionStringSetting = "AzureCosmosDBConnection")
Please make sure you have a variable in the name of "AzureCosmosDBConnection" in your application settings and local.settings.json(if you want to test locally)
Refer - [https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-cosmosdb-v2][1]
The above link has C# example too.

Categories

Resources