1+ // Copyright (c) .NET Foundation. All rights reserved.
2+ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+ using System ;
5+ using System . Collections . Generic ;
6+ using System . Diagnostics . CodeAnalysis ;
7+ using System . Linq ;
8+ using System . Reflection ;
9+ using System . Reflection . Emit ;
10+ using System . Threading . Tasks ;
11+
12+ namespace Microsoft . AspNetCore . SignalR
13+ {
14+ internal static class TypedClientBuilder < T >
15+ {
16+ private const string ClientModuleName = "Microsoft.AspNetCore.SignalR.TypedClientBuilder" ;
17+
18+ // There is one static instance of _builder per T
19+ private static Lazy < Func < IClientProxy , T > > _builder = new Lazy < Func < IClientProxy , T > > ( ( ) => GenerateClientBuilder ( ) ) ;
20+
21+ public static T Build ( IClientProxy proxy )
22+ {
23+ return _builder . Value ( proxy ) ;
24+ }
25+
26+ public static void Validate ( )
27+ {
28+ // The following will throw if T is not a valid type
29+ _ = _builder . Value ;
30+ }
31+
32+ private static Func < IClientProxy , T > GenerateClientBuilder ( )
33+ {
34+ VerifyInterface ( typeof ( T ) ) ;
35+
36+ var assemblyName = new AssemblyName ( ClientModuleName ) ;
37+ var assemblyBuilder = AssemblyBuilder . DefineDynamicAssembly ( assemblyName , AssemblyBuilderAccess . Run ) ;
38+ var moduleBuilder = assemblyBuilder . DefineDynamicModule ( ClientModuleName ) ;
39+ var clientType = GenerateInterfaceImplementation ( moduleBuilder ) ;
40+
41+ return proxy => ( T ) Activator . CreateInstance ( clientType , proxy ) ;
42+ }
43+
44+ private static Type GenerateInterfaceImplementation ( ModuleBuilder moduleBuilder )
45+ {
46+ var type = moduleBuilder . DefineType (
47+ ClientModuleName + "." + typeof ( T ) . Name + "Impl" ,
48+ TypeAttributes . Public ,
49+ typeof ( Object ) ,
50+ new [ ] { typeof ( T ) } ) ;
51+
52+ var proxyField = type . DefineField ( "_proxy" , typeof ( IClientProxy ) , FieldAttributes . Private ) ;
53+
54+ BuildConstructor ( type , proxyField ) ;
55+
56+ foreach ( var method in GetAllInterfaceMethods ( typeof ( T ) ) )
57+ {
58+ BuildMethod ( type , method , proxyField ) ;
59+ }
60+
61+ return type . CreateTypeInfo ( ) ;
62+ }
63+
64+ private static IEnumerable < MethodInfo > GetAllInterfaceMethods ( Type interfaceType )
65+ {
66+ foreach ( var parent in interfaceType . GetInterfaces ( ) )
67+ {
68+ foreach ( var parentMethod in GetAllInterfaceMethods ( parent ) )
69+ {
70+ yield return parentMethod ;
71+ }
72+ }
73+
74+ foreach ( var method in interfaceType . GetMethods ( ) )
75+ {
76+ yield return method ;
77+ }
78+ }
79+
80+ private static void BuildConstructor ( TypeBuilder type , FieldInfo proxyField )
81+ {
82+ var method = type . DefineMethod ( ".ctor" , System . Reflection . MethodAttributes . Public | System . Reflection . MethodAttributes . HideBySig ) ;
83+
84+ var ctor = typeof ( object ) . GetConstructor ( BindingFlags . Instance | BindingFlags . Public | BindingFlags . NonPublic ,
85+ null , new Type [ ] { } , null ) ;
86+
87+ method . SetReturnType ( typeof ( void ) ) ;
88+ method . SetParameters ( typeof ( IClientProxy ) ) ;
89+
90+ var generator = method . GetILGenerator ( ) ;
91+
92+ // Call object constructor
93+ generator . Emit ( OpCodes . Ldarg_0 ) ;
94+ generator . Emit ( OpCodes . Call , ctor ) ;
95+
96+ // Assign constructor argument to the proxyField
97+ generator . Emit ( OpCodes . Ldarg_0 ) ; // type
98+ generator . Emit ( OpCodes . Ldarg_1 ) ; // type proxyfield
99+ generator . Emit ( OpCodes . Stfld , proxyField ) ; // type.proxyField = proxyField
100+ generator . Emit ( OpCodes . Ret ) ;
101+ }
102+
103+ private static void BuildMethod ( TypeBuilder type , MethodInfo interfaceMethodInfo , FieldInfo proxyField )
104+ {
105+ var methodAttributes =
106+ MethodAttributes . Public
107+ | MethodAttributes . Virtual
108+ | MethodAttributes . Final
109+ | MethodAttributes . HideBySig
110+ | MethodAttributes . NewSlot ;
111+
112+ ParameterInfo [ ] parameters = interfaceMethodInfo . GetParameters ( ) ;
113+ Type [ ] paramTypes = parameters . Select ( param => param . ParameterType ) . ToArray ( ) ;
114+
115+ var methodBuilder = type . DefineMethod ( interfaceMethodInfo . Name , methodAttributes ) ;
116+
117+ var invokeMethod = typeof ( IClientProxy ) . GetMethod (
118+ "InvokeAsync" , BindingFlags . Instance | BindingFlags . Public | BindingFlags . NonPublic , null ,
119+ new Type [ ] { typeof ( string ) , typeof ( object [ ] ) } , null ) ;
120+
121+ methodBuilder . SetReturnType ( interfaceMethodInfo . ReturnType ) ;
122+ methodBuilder . SetParameters ( paramTypes ) ;
123+
124+ // Sets the number of generic type parameters
125+ var genericTypeNames =
126+ paramTypes . Where ( p => p . IsGenericParameter ) . Select ( p => p . Name ) . Distinct ( ) . ToArray ( ) ;
127+
128+ if ( genericTypeNames . Any ( ) )
129+ {
130+ methodBuilder . DefineGenericParameters ( genericTypeNames ) ;
131+ }
132+
133+ var generator = methodBuilder . GetILGenerator ( ) ;
134+
135+ // Declare local variable to store the arguments to IClientProxy.InvokeAsync
136+ generator . DeclareLocal ( typeof ( object [ ] ) ) ;
137+
138+ // Get IClientProxy
139+ generator . Emit ( OpCodes . Ldarg_0 ) ;
140+ generator . Emit ( OpCodes . Ldfld , proxyField ) ;
141+
142+ // The first argument to IClientProxy.InvokeAsync is this method's name
143+ generator . Emit ( OpCodes . Ldstr , interfaceMethodInfo . Name ) ;
144+
145+ // Create an new object array to hold all the parameters to this method
146+ generator . Emit ( OpCodes . Ldc_I4 , parameters . Length ) ; // Stack:
147+ generator . Emit ( OpCodes . Newarr , typeof ( object ) ) ; // allocate object array
148+ generator . Emit ( OpCodes . Stloc_0 ) ;
149+
150+ // Store each parameter in the object array
151+ for ( int i = 0 ; i < paramTypes . Length ; i ++ )
152+ {
153+ generator . Emit ( OpCodes . Ldloc_0 ) ; // Object array loaded
154+ generator . Emit ( OpCodes . Ldc_I4 , i ) ;
155+ generator . Emit ( OpCodes . Ldarg , i + 1 ) ; // i + 1
156+ generator . Emit ( OpCodes . Box , paramTypes [ i ] ) ;
157+ generator . Emit ( OpCodes . Stelem_Ref ) ;
158+ }
159+
160+ // Call InvokeAsync
161+ generator . Emit ( OpCodes . Ldloc_0 ) ;
162+ generator . Emit ( OpCodes . Callvirt , invokeMethod ) ;
163+
164+ if ( interfaceMethodInfo . ReturnType == typeof ( void ) )
165+ {
166+ // void return
167+ generator . Emit ( OpCodes . Pop ) ;
168+ }
169+
170+ generator . Emit ( OpCodes . Ret ) ; // Return
171+ }
172+
173+ private static void VerifyInterface ( Type interfaceType )
174+ {
175+ if ( ! interfaceType . IsInterface )
176+ {
177+ throw new InvalidOperationException ( "Type must be an interface." ) ;
178+ }
179+
180+ if ( interfaceType . GetProperties ( ) . Length != 0 )
181+ {
182+ throw new InvalidOperationException ( "Type must not contain properties." ) ;
183+ }
184+
185+ if ( interfaceType . GetEvents ( ) . Length != 0 )
186+ {
187+ throw new InvalidOperationException ( "Type can not contain events." ) ;
188+ }
189+
190+ foreach ( var method in interfaceType . GetMethods ( ) )
191+ {
192+ VerifyMethod ( interfaceType , method ) ;
193+ }
194+
195+ foreach ( var parent in interfaceType . GetInterfaces ( ) )
196+ {
197+ VerifyInterface ( parent ) ;
198+ }
199+ }
200+
201+ private static void VerifyMethod ( Type interfaceType , MethodInfo interfaceMethod )
202+ {
203+ if ( interfaceMethod . ReturnType != typeof ( void ) && interfaceMethod . ReturnType != typeof ( Task ) )
204+ {
205+ throw new InvalidOperationException ( "Method must return Void or Task." ) ;
206+ }
207+
208+ foreach ( var parameter in interfaceMethod . GetParameters ( ) )
209+ {
210+ VerifyParameter ( interfaceType , interfaceMethod , parameter ) ;
211+ }
212+ }
213+
214+ private static void VerifyParameter ( Type interfaceType , MethodInfo interfaceMethod , ParameterInfo parameter )
215+ {
216+ if ( parameter . IsOut )
217+ {
218+ throw new InvalidOperationException ( "Method must not take out parameters." ) ;
219+ }
220+
221+ if ( parameter . ParameterType . IsByRef )
222+ {
223+ throw new InvalidOperationException ( "Method must not take reference parameters." ) ;
224+ }
225+ }
226+ }
227+ }
0 commit comments