Azure SignalRTrigger not working with Azure Functions - c#

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.

Related

How do I send SignalR messages from within an existing Azure function without adding a new Azure function? Can it be done?

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... :-(

CosmosDB Azure function Trigger with SignalR output binding

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.

Why I get Internal Server Error after publishing Azure Function?

I've made an API in Azure Functions that uses an Azure Sql database.
I have this route:
[FunctionName("GetAllClassesForTeacher")]
public IActionResult GetAllClassesForTeacher(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequestMessage req,
ILogger log)
{
var headers = req.Headers;
if (headers.TryGetValues("TeacherId", out var Teacher))
{
int TeacherId = Convert.ToInt32(Teacher.First());
var result = _classDistributionServices.GetAllClassesForTeacher(TeacherId);
if (result != null)
{
return new OkObjectResult(result);
}
return new NotFoundResult();
}
return new NotFoundResult();
}
When tested localy, it works everytime. But after publishing it on Azure Portal, I get back from postman:
{
"statusCode": 500,
"message": "Internal server error",
"activityId": "f98c4112-0c31-4841-99a5-c79dffa41d86"
What I've tried/did until now:
to take the IP from Azure Function and register it in Firewalls and
virtual networks from Azure Sql Server.
I've made sure that the ConnectionString from local.settings.json it
is uploaded on portal.
I've declared this route in API Management.
But still the error persists.
I have a feeling that is still because of database firewall even if I've registered the right IP?
Can you please help me?
There a few way for you to figure out what is the problem:
1-Enable Application Insights and use the Live Metrics to capture the error
2-Use Kudu and check for more details about the error (e.g. https://myfunctionapp.scm.azurewebsites.net/azurejobs/#/functions)
3-You can also wrap your code with a try/catch and throw details of the exception in the response output.

Connect to signalR hub from c# web api

I've been tasked with trying to move our signalR hub to an azure cloud service with a service bus backplane. No problems there. The javascript client is able to get the hubs.js and connect without errors. We also have a web api project that needs to send messages to the hub but I cannot get it to connect. Everything I've tried doesn't work and the connection times out. I must be missing something but, since this is my first time working with signalR and Azure, I don't know what it is. The web api is hosted on IIS.
Here is the code I am trying to use to connect:
private async void InitializeHub()
{
string connectionString = "http://xxxx-xxxxx.cloudapp.net/signalr";
var hubConnection = new HubConnection(connectionString, useDefaultUrl: false);
//var hubConnection = new HubConnection(connectionString);
HubProxy = hubConnection.CreateHubProxy("clientPortalHub");
await hubConnection.Start();
}
Is there some configuration I am missing? Anything need to be done in IIS? I'm not getting any helpful error messages, just that the connection times out. I can hit the url (the real one, not the one I pasted) in a browser and get "Unknown transport".
If it helps here is the startup from the hub:
public void Configuration(IAppBuilder app)
{
// Any connection or hub wire up and configuration should go here
string connectionString = "<omitted>";
GlobalHost.DependencyResolver.UseServiceBus(connectionString, "clientPortalHub");
app.Map("/signalr", map =>
{
map. UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration
{
// You can enable JSONP by uncommenting line below.
// JSONP requests are insecure but some older browsers (and some
// versions of IE) require JSONP to work cross domain
// EnableJSONP = true
};
hubConfiguration.EnableDetailedErrors = true;
map.RunSignalR(hubConfiguration);
});
}

"Microsoft.Office365.Discovery.DiscoveryFailedException: Unauthorized" when creating a DiscoveryClient object

I've got an ASP.Net MVC 5 web application that is attempting to integrate with Office 365/Azure AD.
The application successfully allows Sign In/Out, as well as successfully delegates permission for Graph API calls and calls to a separate Web API of mine which auths against Azure AD. So, something is working.
However, when trying to create a DiscoveryClient object, I am getting the Unauthorised ErrorCode in a DiscoveryFailedException.
The offending method:
public static async Task<SharePointClient> CreateSharePointClientAsync(string capability)
{
var signInUserId = ClaimsPrincipal.Current.FindFirst(System.IdentityModel.Claims.ClaimTypes.NameIdentifier).Value;
var userObjectId = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext ctx = new AuthenticationContext(OfficeSettings.Authority, new NaiveSessionCache(signInUserId));
var credential = new ClientCredential(OfficeSettings.ClientId, OfficeSettings.ClientSecret);
var userIdent = new UserIdentifier(userObjectId, UserIdentifierType.UniqueId);
try
{
DiscoveryClient discoClient = new DiscoveryClient("https://api.office.com/discovery/v1.0/me/",
async () =>
{
var authResult = await ctx.AcquireTokenAsync("https://api.office.com/discovery/",
credential);
return authResult.AccessToken;
});
var capabilityResult = await discoClient.DiscoverCapabilityAsync(capability);
return new SharePointClient(capabilityResult.ServiceEndpointUri,
async () =>
{
var authResult = await ctx.AcquireTokenAsync(capabilityResult.ServiceResourceId,
credential);
return authResult.AccessToken;
});
}
catch (AdalException ex)
{
if (ex.ErrorCode == AdalError.FailedToAcquireTokenSilently)
{
ctx.TokenCache.Clear();
throw ex;
}
return null;
}
catch (Exception ex)
{
throw ex;
}
}
The error occurs at the call to ctx.AcquireTokenAsync in the lambda of the DiscoveryClient instantiation.
As above, the application can sign in users, so the ClientId/Secret must be correct and I also believe the permissions required for this are similar to what is required to use the Discovery Service.
I've also tried to skip the discovery step and just hard-code the ResourceId for the Sharepoint client to see if I can leave it like this for the time being; however, I'm also getting some permission errors here, too, despite the appropriate permissions being granted for the application in the AAD management page.
I'm at a complete loss; running the example application from O365-ASPNETMVC-Start works as you'd expect using the same credentials to sign in to Office 365.
To try and pinpoint the issue, the (what I believe to be the relevant) code is now practically identical to the (working) example, with still no luck.
If any other code would be useful in solving the issue, I'll happily share.
Any ideas would be hugely appreciated.
To start, I haven't tried to alter the O365-ASPNETMVC-Start sample to work with the unified API endpoint (Graph API). While I haven't tried what you're doing, I'd expect the discovery calls to not work when targeting the unified API. The discoveryClient was useful when you needed to discover the endpoint. Since there is only one endpoint, there is no need to discover it. I suggest you take a look at Office 365 unified API overview (preview) to get some more background.
I suggest you take a look at the
Office 365 Profile sample for Windows as it uses the unified endpoint. It uses the unified API client library that you'll want to use if targeting the unified endpoint.
Do note that the unified API is in preview. The APIs used by the O365-ASPNETMVC-Start sample are what is currently endorsed for production use.
Please tag your question with the [Office365] tag so that other interested people can see your question.
With regards,

Categories