Skip to content

Conversation

@zackliu
Copy link
Member

@zackliu zackliu commented Mar 10, 2020

Here is the C# chat sample to demonstrate new feature: SignalR Trigger
The trigger can collaborate with SignalR Service's new upstream feature smoothly to empower customer to handle messages from service in serverless mode.

Now, client can call connection.send() or connection.invoke() just like what they usually do in default mode.
Also, in C#, Function can design there own attribute to implement Authorization. In the sample, a very simple attribute that only allow the admin to call broadcast is demonstrated.

It's not the final design, please give some suggestions. And I will add a JavaScript sample later.


[FunctionAuthorize]
[FunctionName("broadcast")]
public static async Task Broadcast([SignalRTrigger(Hub, Category.Messages, "broadcast", "message")]InvocationContext invocationContext,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

broadcast [](start = 84, length = 9)

How about enum messages event as well. the functionality is limited to our supported APIs.

{
private const string AdminKey = "admin";

public override Task OnExecutingAsync(FunctionExecutingContext executingContext, CancellationToken cancellationToken)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we extract some common logic into a base class so that developer only needs to focus on the claim validation part.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's good. I will use another PR to do that.


[FunctionAuthorize]
[FunctionName("broadcast")]
public static async Task Broadcast([SignalRTrigger(Hub, Category.Messages, "broadcast", "message")]InvocationContext invocationContext,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to get method name from FunctionName attribute?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function name need to be globally unique, but method name just need to be unique in hub level

@davidfowl
Copy link
Member

The way I see it, we need to get as close to possible to a Hub as we can on with the Azure functions infrastructure and see what that looks like in multiple languages. I have some ideas of what a nice node and C# experience looks like.

@zackliu
Copy link
Member Author

zackliu commented Mar 11, 2020

@davidfowl I've just updated the sample and applied class base on it.

Briefly, when Functions are defined in under a class that derived from ServerlessHub, many configurations work can be simplified.

[FunctionName(nameof(SendToConnection))]
public async Task SendToConnection([SignalRTrigger]InvocationContext invocationContext,
    [SignalRParameter]string connectionId,
    [SignalRParameter]string message)
{
    await Clients.Client(connectionId).SendAsync(NewMessageTarget, new NewMessage(invocationContext, message));
}

Considered the balance of convenience and correction, [SignalRParameter] is used to make it more controllable and avoid conflict with other binding's binding data.

However, all these simplify can only be applied in C#. As the limitation of Function architecture, we can do less in other language. I will then open another PR to demonstrate trigger in nodejs.

@davidfowl
Copy link
Member

Can we get rid of [SignalRParameter] and move InvocationContext to a Property on the base class?

@zackliu
Copy link
Member Author

zackliu commented Mar 13, 2020

@davidfowl Getting rid of [SignalRParameter] makes the trigger hand to decide which parameter the customer want to bind by signar tigger. Customers usually use other input/output bindings in Azure Function. Using [SignalRParameter] is a little tedious but clear and easy for learning.
InvocationContext is directly passed to the Function method. Seems it's hard to get it in the ctor and assign to a Property.

@davidfowl
Copy link
Member

davidfowl commented Mar 13, 2020

They want to bring everything that isn’t already marked. Mark the things that are non signalr explicitly.

private const string NewConnectionTarget = "newConnection";

[FunctionName("negotiate")]
public static SignalRConnectionInfo GetSignalRInfo(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be moved into ServerlessHub?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's good. But it needs some investigation. I will put it into another RP.

}

[FunctionName(nameof(Disconnect))]
public void Disconnect([SignalRTrigger]InvocationContext invocationContext)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you call these methods explicitly?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's your "explicitly" means?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can these calls come from outside the signalr service?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the client trigger a call to disconnect and not be disconnected?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, as long as the message sent to SignalR trigger can follow our serverless spec.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If using SignalR Service, it won't happen. Because all message from client's invocation will be in the messages category. And connected/disconnected are connections category

/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public class SignalRParameterAttribute : Attribute
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also allow specify parameter name

Copy link
Member Author

@zackliu zackliu Mar 13, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's useless. The parameter name is used to match the actual parameter name in method. It should be exactly the name it decorates. We should avoid user using like

void Function([SignalRTrigger]InvocationContext context, [SignalRParameter("arg1")]arg0, [SignalRParameter("arg0")]arg1)

It just cause confusing.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like in JSON serialization, you can use JsonProperty to give property a different name (for example a short name). But seems SignalR itself doesn't support this as well. So it should be ok to not support it for now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then do we really need this? A parameter is by default recognized as a SignalR parameter.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only in class base. It prepared for non-class based function. It's still better than using ParameterNames directly

$"{nameof(SignalRTriggerAttribute)}.{nameof(SignalRTriggerAttribute.ParameterNames)} and {nameof(SignalRParameterAttribute)} can not be set in the same Function.");
}

parameterNames = parameterNamesFromAttribute.Count != 0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should check parameterNames first? parametersNames should have higher priority than attributes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want parametersNames and attribute are set together. So, there's no priority issue.

data.messages.unshift(message);
};
function onNewConnection(message) {
data.myConnectionId = message.ConnectionId;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to do a camel case conversion for class members? (ConnectionId -> connectionId) I think SignalR by default does this conversion.


private bool IsLegalClassBasedParameter(ParameterInfo parameter)
{
// In class based model, we treat all the parameters as a legal parameter exception the cases below
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exception [](start = 86, length = 9)

typo: except

{
/// <summary>
/// In class based model, mark the parameter explicitly not to be a SignalR parameter.
/// That means it won't be bind to a InvocationMessage argument.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

be bind [](start = 28, length = 7)

typo: bind or be bound

Copy link
Contributor

@JialinXin JialinXin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@zackliu zackliu merged commit c1f7c46 into dev Mar 16, 2020
}

[FunctionName(nameof(Disconnect))]
public void Disconnect([SignalRTrigger]InvocationContext invocationContext)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be disconnected?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use another PR to do that change including onconnected

UserGroups = hubContext.UserGroups;
}

public IHubClients Clients { get; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about also add them to InvocationContext?

}

[FunctionName(nameof(Connected))]
public async Task Connected([SignalRTrigger]InvocationContext invocationContext, ILogger logger)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you still specify hub name in SignalRTrigger?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants