I am using the Azure SignalR Service in combination with Azure Functions and I have the following code:
public class SignalRHubFunction
{
[FunctionName("SignalRConnected")]
public async Task Run([SignalRTrigger("myhubname", "connections", "connected", ConnectionStringSetting = "AzureSignalRConnectionString")] InvocationContext invocationContext, ILogger logger)
{
logger.LogInformation($"{invocationContext.ConnectionId} connected");
}
}
I have a hard time getting a trigger on the 'OnConnected' event. Samples are only given with Class based model.
And the docs aren't really helpful for me.
Docs are telling me the category parameter of the SignalRTrigger constructor should be: connections or messages.
So I use connections.
This value must be set as the category of messages for the function to be triggered. The category can be one of the following values:
connections: Including connected and disconnected events
messages: Including all other events except those in connections category
I don't really understand what the docs mean with the event parameter
This value must be set as the event of messages for the function to be triggered. For messages category, event is the target in invocation message that clients send. For connections category, only connected and disconnected is used.
I guess they are saying you can choose between connected and disconnected.
However with the code from above the trigger is never hit. Any thoughts?
Original Answer: https://learn.microsoft.com/en-us/answers/questions/159266/debug-function-using-a-signalrtrigger.html
For the SignalRTrigger to work, you need to set a webhook to the function in SignalR.
When you deploy a function with the SignalRTrigger, it does 2 extra things:
Create the webhook: https://<APP_NAME>.azurewebsites.net/runtime/webhooks/signalr
Create an API key code (signalr_extension) in the function app system settings (see "system keys" section in "App Keys" blade of the azure portal Function App)
SignalR POSTs events to this webhook (so obviously this doesn't work on local, unless you have a publicly addressable IP that you can add to SignalR).
To configure the SignalR event webhook, go to "Settings" blade of SignalR and add the upstream URL https://<APP_NAME>.azurewebsites.net/runtime/webhooks/signalr?code=<API_KEY>
Voila! This should now work
Ref: https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-signalr-service-trigger?tabs=javascript#send-messages-to-signalr-service-trigger-binding
This code snippet works.
[FunctionName("OnConnected ")]
public async Task OnConnected ([SignalRTrigger("YourHub", "connections", "connected ")]
InvocationContext invocationContext, ILogger logger)
{
logger.LogInformation($"{invocationContext.ConnectionId} has connected");
}
Also configure, Upstream URL in Azure SignalR settings
<Function_App_URL>/runtime/webhooks/signalr?code=<API_KEY>
SignalR Service integration
The Function_App_URL can be found on Function App's Overview page and The API_KEY is generated by Azure Function. You can get the API_KEY from signalr_extension in the App keys blade of Function App.
Related
I found this great Getting Started Guide for the Azure Blob Storage SDK and how to connect to my storage account.
A quick prototype showed that it already works, but I want to ensure this and the logic behind it via tests (either unit or integration tests).
I found this resource on an Azure Testing Library that can record HTTP requests of a pipeline and was wondering whether this is applicable to the Blob Storage SDK as well?
Are there any other options to properly test my applications code interaction with the Blob Storage SDK?
My idea would for example be:
Call a method on my client with a parameter
Take the blob name from the passed parameter and make a call to the blob storage container
Validate that the call was made to the correct container and blob via a test case
• I too just tried to follow the documentation link for various tasks to be performed regarding the Azure Blob storage using the .Net v12 SDK and the results were successful as follows: -
Also, if you want to call a method/task on your client/application using a parameter with respect to Blob storage, you can surely do so by using the ‘BlobServiceClient’ class. To know more on how to use it, please refer to the documentation link below: -
https://azure.github.io/azure-sdk-korean/dotnet_introduction.html
It clearly states on how to call the service name client and the methods for which to use the parameters for performing various tasks as shown in the sample code from that document below: -
namespace Azure.<group>.<service_name> {
// main service client class
public class <service_name>Client {
// simple constructors; don't use default parameters
public <service_name>Client(<simple_binding_parameters>);
public <service_name>Client(<simple_binding_parameters>, <service_name>ClientOptions options);
// 0 or more advanced constructors
public <service_name>Client(<advanced_binding_parameters>, <service_name>ClientOptions options = default);
// mocking constructor
protected <service_name>Client();
// service methods (synchronous and asynchronous)
public virtual Task<Response<<model>> <service_operation>Async(<parameters>, CancellationToken cancellationToken = default);
public virtual Response<model> <service_operation>(<parameters>, CancellationToken cancellationToken = default);
// other members
}
// options for configuring the client
public class <service_name>ClientOptions : ClientOptions {
}
}
Also, I would suggest you to please refer this community thread: -
Call .NET Web API method whenever a new file is added to Azure Blob Storage
As a follow-up to this question, I wanted to understand how my invoking of a Service manually can be improved. This became longer than I wanted, but I feel the background info is needed.
When doing a pub/sub (broadcast), the normal sequence and flow in the Messaging API isn't used, and I instead get a callback when a pub/sub message is received, using IRedisClient, IRedisSubscription:
_subscription.OnMessage = (channel, msg) =>
{
onMessageReceived(ParseJsonMsgToPoco(msg));
};
The Action onMessageReceived will then, in turn, invoke a normal .NET/C# Event, like so:
protected override void OnMessageReceived(MyRequest request)
{
OnMyEvent?.Invoke(this, new RequestEventArgs(request));
}
This works, I get my request and all that, however, I would like it to be streamlined into the other flow, the flow in the Messaging API, meaning, the request finds its way into a Service class implementation, and that all normal boilerplate and dependency injection takes place as it would have using Messaging API.
So, in my Event handler, I manually invoke the Service:
private void Instance_OnMyEvent(object sender, RequestEventArgs e)
{
using (var myRequestService = HostContext.ResolveService<MyRequestService>(new BasicRequest()))
{
myRequestService.Any(e.Request);
}
}
and the MyRequestService is indeed found and Any called, and dependency injection works for the Service.
Question 1:
Methods such as OnBeforeExecute, OnAfterExecute etc, are not called, unless I manually call them, like: myRequestService.OnBeforeExecute(e) etc. What parts of the pipeline is lost? Can it be reinstated in some easy way, so I don't have to call each of them, in order, manually?
Question 2:
I think I am messing up the DI system when I do this:
using (var myRequestService = HostContext.ResolveService<MyRequestService>(new BasicRequest()))
{
myRequestService.OnBeforeExecute(e.Request);
myRequestService.Any(e.Request);
myRequestService.OnAfterExecute(e.Request);
}
The effect I see is that the injected dependencies that I have registered with container.AddScoped, isn't scoped, but seems static. I see this because I have a Guid inside the injected class, and that Guid is always the same in this case, when it should be different for each request.
container.AddScoped<IRedisCache, RedisCache>();
and the OnBeforeExecute (in a descendant to Service) is like:
public override void OnBeforeExecute(object requestDto)
{
base.OnBeforeExecute(requestDto);
IRedisCache cache = TryResolve<IRedisCache>();
cache?.SetGuid(Guid.NewGuid());
}
So, the IRedisCache Guid should be different each time, but it isn't. This however works fine when I use the Messaging API "from start to finish". It seems that if I call the TryResolve in the AppHostBase descendant, the AddScoped is ignored, and an instance is placed in the container, and then never removed.
What parts of the pipeline is lost?
None of the request pipeline is executed:
myRequestService.Any(e.Request);
Is physically only invoking the Any C# method of your MyRequestService class, it doesn't (nor cannot) do anything else.
The recommended way for invoking other Services during a Service Request is to use the Service Gateway.
But if you want to invoke a Service outside of a HTTP Request you can use the RPC Gateway for executing non-trusted services as it invokes the full Request Pipeline & converts HTTP Error responses into Typed Error Responses:
HostContext.AppHost.RpcGateway.ExecuteAsync()
For executing internal/trusted Services outside of a Service Request you can use HostContext.AppHost.ExecuteMessage as used by ServiceStack MQ which applies Message Request Request/Response Filters, Service Action Filters & Events.
I have registered with container.AddScoped
Do not use Request Scoped dependencies outside of a HTTP Request, use Singleton if the dependencies are ThreadSafe, otherwise register them as Transient. If you need to pass per-request storage pass them in IRequest.Items.
I try to create credintals or claims by different way to SignalR client, for example this way
connection = new HubConnectionBuilder().WithUrl(URL, opt => opt.Headers.Add("Bearer", myJWT)).
WithAutomaticReconnect(new[] { TimeSpan.FromSeconds(10) }).
Build();
But in my server hub I don't see anything in my Hub in this callback
public override Task OnConnectedAsync()
Connection established normally, I see it but in "Connect" object and "hubContext" object don't see anymore (user, headers, identity) its empty.
I have no special security configuration of my SignalR service, I have configure only SignalR endpoint, but I would expect to see at least client headers in my server hub, but no.
You can get that in OnConnectedAsync method by using this property:
Context.GetHttpContext().Request
And just a heads up, you want to do authorization header like this:
opt.Headers.Add("Authorization", myJWT)
Then grab it like this:
var token = Context.GetHttpContext().Request.Headers["Authorization"];
is there any way to get current signalR request user outside the hub? I can use Hub.Context.User inside of hub methods, but if hub method calls any other underlying layer? Wcf service call - an additional BehaviorExtensionElement is used to add wcf message header with current user identity name.
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
request.Headers.Add(MessageHeader.CreateHeader(
Constants.HeaderNames.SessionId,
Constants.HeaderNames.HeaderNamespace,
_hubManager.ResolveHub(Constants.Hubs.MessengerHub).
Context.User.Identity.Name));
}
Yes, i found that the DefaultHubManager gets the Hub, but i'm not sure it will be the hub from current request, not the concurrent one or a new one, 'cause at the end of ResolveHub i see the following code runs
return (DependencyResolverExtensions.Resolve(this._resolver, descriptor.HubType)
?? Activator.CreateInstance(descriptor.HubType)) as IHub;
Obviuosly i can pass user from hub method to wcf call but it requires refactoring to move from wcf behaviour to setting request field with user name explicitly.
Also can i rely on HttpContext.Current to get the info from cookie?
No you cannot. The only way to retrieve the currently active user outside of the hub itself would be to pass the users information to whatever method you call in the hub.
A common pattern is to track your users by adding them to some sort of dictionary in OnConnected and removing them in OnDisconnected. You can then have an entirely separate way of identifying your users while having required information that's associated with them.
Just getting my head around message queues and Redis MQ, excellent framework.
I understand that you have to use .RegisterHandler(...) to determine which handler will process the type of message/event that is in the message queue.
So if I have EventA, EventB etc should I have one Service which handles each of those Events, like :
public class DomainService : Service {
public object Any(EventA eventA) {...}
public object Any(EventB eventA) {...}
}
So these should be only queue/redis list created?
Also, what If I want a chain of events to happen, so for example a message of type EventA also has a handler that sends an Email providing handlers earlier on the chain are successful?
ServiceStack has no distinction between services created for MQ's, REST, HTML or SOAP services, they're the same thing. i.e. they each accept a Request DTO and optionally return a Response DTO and the same service can handle calls from any endpoint or format, e.g HTML, REST, SOAP or MQ.
Refer to ServiceStack's Architecture diagram to see how MQ fits in.
Limitations
The only things you need to keep in mind are:
Like SOAP, MQ's only support 1 Verb so your methods need to be named Post or Any
Only Action Filters are executed (i.e. not Global or Attribute filters)
You get MqRequest and MqResponse stubs in place of IHttpRequest, IHttpResponse. You can still use .Items to pass data through the request pipeline but any HTTP actions like setting cookies or HTTP Headers are benign
Configuring a Redis MQ Host
The MQ Host itself is completely decoupled from the rest of the ServiceStack framework, who doesn't know the MQ exists until you pass the message into ServiceStack yourself, which is commonly done inside your registered handler, e.g:
var redisFactory = new PooledRedisClientManager("localhost:6379");
var mqHost = new RedisMqServer(redisFactory, retryCount:2);
mqHost.RegisterHandler<Hello>(m => {
return this.ServiceController.ExecuteMessage(m);
});
//shorter version:
//mqHost.RegisterHandler<Hello>(ServiceController.ExecuteMessage);
mqHost.Start(); //Starts listening for messages
In your RegisterHandler<T> you specify the type of Request you want it to listen for.
By default you can only Register a single handler for each message and in ServiceStack a Request is tied to a known Service implementation, in the case of MQ's it's looking for a method signature first matching: Post(Hello) and if that doesn't exist it looks for the fallback Any(Hello).
Can add multiple handlers per message yourself
If you want to invoke multiple handlers then you would just maintain your own List<Handler> and just go through and execute them all when a request comes in.
Calling different services
If you want to call a different service, just translate it to a different Request DTO and pass that to the ServiceController instead.
When a MQ Request is sent by anyone, e.g:
mqClient.Publish(new Hello { Name = "Client" });
Your handler is invoked with an instance of type IMessage where the Request DTO is contained in the Body property. At that point you can choose to discard the message, validate it or alter it.
MQ Requests are the same as any other Service requests
In most cases you would typically just forward the message on to the ServiceController to process, the implementation of which is:
public object ExecuteMessage<T>(IMessage<T> mqMsg)
{
return Execute(mqMsg.Body, new MqRequestContext(this.Resolver, mqMsg));
}
The implementation just extracts the Request DTO from the mqMsg.Body and processes that message as a normal service being passed a C# Request DTO from that point on, with a MqRequestContext that contains the MQ IHttpRequest, IHttpResponse stubs.