Repository navigation
Nullability in SignalR - #29219
Nullability in SignalR#29219
Conversation
| /// Gets the user for this connection. | ||
| /// </summary> | ||
| public virtual ClaimsPrincipal? User => Features.Get<IConnectionUserFeature>()?.User; | ||
| public virtual ClaimsPrincipal User => Features.Get<IConnectionUserFeature>()?.User ?? new ClaimsPrincipal(); |
There was a problem hiding this comment.
Not sure what to do here. Cache the User?
The reason I'm not keeping it null is because it's nicer to deal with an empty ClaimsPrincipal than a null one. An empty one will not be authenticated in any way or have any claims. The Auth types we use don't have null annotations for the user as the expectation is that you'll at least pass an empty one.
There was a problem hiding this comment.
Assign blank ClaimsPrincipal to underlying IConnectionUserFeature when the property is accessed?
There was a problem hiding this comment.
The IConnectionUserFeature could come from anyone (in theory), and the interface allows a null user. Plus the interface itself could not be on the connection.
|
Will review when PR is out of draft 🥇 |
| /// </returns> | ||
| [SuppressMessage("ApiDesign", "RS0026:Do not add multiple overloads with optional parameters", Justification = "Required to maintain compatibility")] | ||
| public static Task<TResult> InvokeAsync<TResult>(this HubConnection hubConnection, string methodName, object arg1, CancellationToken cancellationToken = default) | ||
| public static Task<TResult> InvokeAsync<TResult>(this HubConnection hubConnection, string methodName, object? arg1, CancellationToken cancellationToken = default) |
There was a problem hiding this comment.
Should these be Task<TResult?>
There was a problem hiding this comment.
I don't think so? That would force callers to always handle null even if they never expect it to be null. If they use a nullable for TResult then that means they expect a null value and should handle it, and the compiler will let them know that. So seems better to let the user decide.
There was a problem hiding this comment.
Fair enough. It's effectively the same choice we made with JSInterop. But we also do not provide any guarantees against it which is crummy.
| } | ||
|
|
||
| return reader.GetString(); | ||
| return reader.GetString()!; |
There was a problem hiding this comment.
No it isn't, we check above that the token type is a string. If the string is null it would have token type null.
| return hubConnection.On(methodName, | ||
| new[] { typeof(T1), typeof(T2), typeof(T3) }, | ||
| args => handler((T1)args[0], (T2)args[1], (T3)args[2])); | ||
| args => handler((T1)args[0]!, (T2)args[1]!, (T3)args[2]!)); |
There was a problem hiding this comment.
Hmm, I wonder if there is a better way to avoid all these bangs.
| public override string ConnectionId | ||
| public override string? ConnectionId | ||
| { | ||
| #pragma warning disable CS8764 // Nullability of return type doesn't match overridden member (possibly because of nullability attributes). |
There was a problem hiding this comment.
Should ConnectionId be made nullable on ConnectionContext?
There was a problem hiding this comment.
I really think that people should be able to rely on ConnectionContext.ConnectionId being non-null. I'm thinking this should just throw if someone tries to access this property before StartAsync() completes.
This implementation of ConnectionContext.ConnectionId is already a bit odd in that its setter throws, so I don't think throwing from the getter when the connection hasn't started is a big deal.
There was a problem hiding this comment.
people should be able to rely on
ConnectionContext.ConnectionIdbeing non-null
It's not just if you access it before starting, if you skip negotiation then this will be null as well.
|
|
||
| /// <inheritdoc /> | ||
| public override Task SendAllAsync(string methodName, object?[]? args, CancellationToken cancellationToken = default) | ||
| public override Task SendAllAsync(string methodName, object?[] args, CancellationToken cancellationToken = default) |
There was a problem hiding this comment.
Is this a change from .NET 5? There is an aspnetcore annoucement issue for nullability changes.
There was a problem hiding this comment.
This is an API change, however passing null would have thrown
| { | ||
| if (_user is null) | ||
| { | ||
| _user = Features.Get<IConnectionUserFeature>()?.User ?? new ClaimsPrincipal(); |
42cdc0c to
5c0693f
Compare
| /// Gets or sets the URL used to send HTTP requests. | ||
| /// </summary> | ||
| public Uri Url { get; set; } | ||
| public Uri Url { get; set; } = default!; |
There was a problem hiding this comment.
var httpConnectionOptions = new HttpConnectionOptions();
var urlString = httpConnectionOptions.Url.ToString();The above throws, right? If so, I think this should be marked nullable.
I might feel differently if it was super common for developer to call the HttpConnectionOptions.Url getter and in practice it was always set, but I expect most developers just call WithUrl() and never touch this property.
There was a problem hiding this comment.
The reason I changed it was because of "! fatigue", everywhere we access this property internally it will be non-null
There was a problem hiding this comment.
I do appreciate trying to avoid "! fatigue" but I don't think that's enough of a reason to misrepresent the nullability of a public property. We could add an internal non-nullable property that mirrors Url and throws if it is null as a sanity check to avoid the fatigue.
There was a problem hiding this comment.
We do this in places where something could be null, but for all typical usage it won't be.
There was a problem hiding this comment.
Right, that's why I said this initially:
I might feel differently if it was super common for developer to call the HttpConnectionOptions.Url getter and in practice it was always set, but I expect most developers just call WithUrl() and never touch this property.
There was a problem hiding this comment.
I expect most developers just call WithUrl() and never touch this property
Right, that makes it less of an issue if we lie with the API.
There was a problem hiding this comment.
I would argue that if it's not a huge issue either way, we should default to no lying with the API.
There was a problem hiding this comment.
I'll try out a private member so we can avoid using ! everywhere in HttpConnection then.
5aa9387 to
39d798f
Compare
Fixes #25612
Fixes #28954