11// Licensed to the .NET Foundation under one or more agreements.
22// The .NET Foundation licenses this file to you under the MIT license.
33
4+ using System . ComponentModel . DataAnnotations ;
45using System . Diagnostics ;
56using System . Diagnostics . CodeAnalysis ;
67using System . Linq ;
78using System . Reflection ;
89using System . Security . Claims ;
10+ using System . Text . Json ;
911using System . Threading . Channels ;
1012using Microsoft . AspNetCore . Authorization ;
13+ using Microsoft . AspNetCore . Http . Validation ;
1114using Microsoft . AspNetCore . Internal ;
1215using Microsoft . AspNetCore . Shared ;
1316using Microsoft . AspNetCore . SignalR . Protocol ;
1417using Microsoft . Extensions . DependencyInjection ;
1518using Microsoft . Extensions . Internal ;
1619using Microsoft . Extensions . Logging ;
20+ using Microsoft . Extensions . Options ;
1721using Log = Microsoft . AspNetCore . SignalR . Internal . DefaultHubDispatcherLog ;
1822
1923namespace Microsoft . AspNetCore . SignalR . Internal ;
@@ -32,6 +36,7 @@ internal sealed partial class DefaultHubDispatcher<[DynamicallyAccessedMembers(H
3236 private readonly Func < HubLifetimeContext , Task > ? _onConnectedMiddleware ;
3337 private readonly Func < HubLifetimeContext , Exception ? , Task > ? _onDisconnectedMiddleware ;
3438 private readonly HubLifetimeManager < THub > _hubLifetimeManager ;
39+ private readonly ValidationOptions ? _validationOptions ;
3540
3641 [ FeatureSwitchDefinition ( "Microsoft.AspNetCore.SignalR.Hub.IsCustomAwaitableSupported" ) ]
3742 [ FeatureGuard ( typeof ( RequiresDynamicCodeAttribute ) ) ]
@@ -40,13 +45,15 @@ internal sealed partial class DefaultHubDispatcher<[DynamicallyAccessedMembers(H
4045 AppContext . TryGetSwitch ( "Microsoft.AspNetCore.SignalR.Hub.IsCustomAwaitableSupported" , out bool customAwaitableSupport ) ? customAwaitableSupport : true ;
4146
4247 public DefaultHubDispatcher ( IServiceScopeFactory serviceScopeFactory , IHubContext < THub > hubContext , bool enableDetailedErrors ,
43- bool disableImplicitFromServiceParameters , ILogger < DefaultHubDispatcher < THub > > logger , List < IHubFilter > ? hubFilters , HubLifetimeManager < THub > lifetimeManager )
48+ bool disableImplicitFromServiceParameters , ILogger < DefaultHubDispatcher < THub > > logger , List < IHubFilter > ? hubFilters , HubLifetimeManager < THub > lifetimeManager ,
49+ ValidationOptions ? validationOptions )
4450 {
4551 _serviceScopeFactory = serviceScopeFactory ;
4652 _hubContext = hubContext ;
4753 _enableDetailedErrors = enableDetailedErrors ;
4854 _logger = logger ;
4955 _hubLifetimeManager = lifetimeManager ;
56+ _validationOptions = validationOptions ;
5057 DiscoverHubMethods ( disableImplicitFromServiceParameters ) ;
5158
5259 var count = hubFilters ? . Count ?? 0 ;
@@ -343,6 +350,11 @@ await SendInvocationError(hubMethodInvocationMessage.InvocationId, connection,
343350 return true ;
344351 }
345352
353+ if ( ! await ValidateArguments ( descriptor , hubMethodInvocationMessage , connection ) )
354+ {
355+ return true ;
356+ }
357+
346358 try
347359 {
348360 var clientStreamLength = hubMethodInvocationMessage . StreamIds ? . Length ?? 0 ;
@@ -687,6 +699,41 @@ private static async Task<bool> IsHubMethodAuthorizedSlow(IServiceProvider provi
687699 return authorizationResult . Succeeded ;
688700 }
689701
702+ private async Task < bool > ValidateArguments ( HubMethodDescriptor hubMethodDescriptor , HubMethodInvocationMessage hubMethodInvocationMessage , HubConnectionContext connection )
703+ {
704+ if ( _validationOptions == null || _validationOptions . Resolvers . Count == 0 )
705+ {
706+ return true ;
707+ }
708+
709+ var validateContext = new ValidateContext ( )
710+ {
711+ ValidationOptions = _validationOptions ,
712+ CurrentValidationPath = $ "{ _fullHubName } .{ hubMethodInvocationMessage . Target } ",
713+ } ;
714+
715+ for ( var i = 0 ; i < hubMethodDescriptor . ParameterInfos . Count ; i ++ )
716+ {
717+ validateContext . ValidationContext = new ValidationContext ( hubMethodInvocationMessage . Arguments [ i ] , serviceProvider : null , items : null )
718+ {
719+ DisplayName = hubMethodDescriptor . ParameterInfos [ i ] . Name ,
720+ } ;
721+ if ( _validationOptions . TryGetValidatableParameterInfo ( hubMethodDescriptor . ParameterInfos [ i ] , out var parameterValidator ) )
722+ {
723+ await parameterValidator . ValidateAsync ( hubMethodInvocationMessage . Arguments [ i ] , validateContext , default ) ;
724+ }
725+ }
726+
727+ if ( validateContext . ValidationErrors is { Count : > 0 } )
728+ {
729+ await SendInvocationError ( hubMethodInvocationMessage . InvocationId , connection ,
730+ $ "Failed to invoke '{ hubMethodInvocationMessage . Target } ' because of validation errors: { JsonSerializer . Serialize ( validateContext . ValidationErrors ) } ") ;
731+ return false ;
732+ }
733+
734+ return true ;
735+ }
736+
690737 private async Task < bool > ValidateInvocationMode ( HubMethodDescriptor hubMethodDescriptor , bool isStreamResponse ,
691738 HubMethodInvocationMessage hubMethodInvocationMessage , HubConnectionContext connection )
692739 {
0 commit comments