PageRenderTime 62ms CodeModel.GetById 40ms app.highlight 7ms RepoModel.GetById 13ms app.codeStats 0ms

/SignalR/Hubs/Lookup/ReflectedMethodDescriptorProvider.cs

https://github.com/kpmrafeeq/SignalR
C# | 143 lines | 97 code | 17 blank | 29 comment | 8 complexity | 82e1849b585544d6fceb84175d561823 MD5 | raw file
  1using System;
  2using System.Collections.Concurrent;
  3using System.Collections.Generic;
  4using System.Globalization;
  5using System.Linq;
  6using System.Reflection;
  7using Newtonsoft.Json.Linq;
  8using SignalR.Infrastructure;
  9
 10namespace SignalR.Hubs
 11{
 12    public class ReflectedMethodDescriptorProvider : IMethodDescriptorProvider
 13    {
 14        private readonly ConcurrentDictionary<string, IDictionary<string, IEnumerable<MethodDescriptor>>> _methods;
 15        private readonly ConcurrentDictionary<string, MethodDescriptor> _executableMethods;
 16
 17        public ReflectedMethodDescriptorProvider()
 18        {
 19            _methods = new ConcurrentDictionary<string, IDictionary<string, IEnumerable<MethodDescriptor>>>(StringComparer.OrdinalIgnoreCase);
 20            _executableMethods = new ConcurrentDictionary<string, MethodDescriptor>(StringComparer.OrdinalIgnoreCase);
 21        }
 22
 23        public IEnumerable<MethodDescriptor> GetMethods(HubDescriptor hub)
 24        {
 25            return FetchMethodsFor(hub)
 26                .SelectMany(kv => kv.Value)
 27                .ToList();
 28        }
 29
 30        /// <summary>
 31        /// Retrieves an existing dictionary of all available methods for a given hub from cache.
 32        /// If cache entry does not exist - it is created automatically by BuildMethodCacheFor.
 33        /// </summary>
 34        /// <param name="hub"></param>
 35        /// <returns></returns>
 36        private IDictionary<string, IEnumerable<MethodDescriptor>> FetchMethodsFor(HubDescriptor hub)
 37        {
 38            return _methods.GetOrAdd(
 39                hub.Name,
 40                key => BuildMethodCacheFor(hub));
 41        }
 42
 43        /// <summary>
 44        /// Builds a dictionary of all possible methods on a given hub.
 45        /// Single entry contains a collection of available overloads for a given method name (key).
 46        /// This dictionary is being cached afterwards.
 47        /// </summary>
 48        /// <param name="hub">Hub to build cache for</param>
 49        /// <returns>Dictionary of available methods</returns>
 50        private IDictionary<string, IEnumerable<MethodDescriptor>> BuildMethodCacheFor(HubDescriptor hub)
 51        {
 52            return ReflectionHelper.GetExportedHubMethods(hub.Type)
 53                .GroupBy(GetMethodName, StringComparer.OrdinalIgnoreCase)
 54                .ToDictionary(group => group.Key,
 55                              group => group.Select(oload =>
 56                                  new MethodDescriptor
 57                                  {
 58                                      ReturnType = oload.ReturnType,
 59                                      Name = group.Key,
 60                                      Invoker = oload.Invoke,
 61                                      Hub = hub,
 62                                      Parameters = oload.GetParameters()
 63                                          .Select(p => new ParameterDescriptor
 64                                              {
 65                                                  Name = p.Name,
 66                                                  Type = p.ParameterType,
 67                                              })
 68                                          .ToList()
 69                                  }),
 70                              StringComparer.OrdinalIgnoreCase);
 71        }
 72
 73        /// <summary>
 74        /// Searches the specified <paramref name="hub">Hub</paramref> for the specified <paramref name="method"/>.
 75        /// </summary>
 76        /// <remarks>
 77        /// In the case that there are multiple overloads of the specified <paramref name="method"/>, the <paramref name="parameter">parameter set</paramref> helps determine exactly which instance of the overload should be resolved. 
 78        /// If there are multiple overloads found with the same number of matching paramters, none of the methods will be returned because it is not possible to determine which overload of the method was intended to be resolved.
 79        /// </remarks>
 80        /// <param name="hub">Hub to search for the specified <paramref name="method"/> on.</param>
 81        /// <param name="method">The method name to search for.</param>
 82        /// <param name="descriptor">If successful, the <see cref="MethodDescriptor"/> that was resolved.</param>
 83        /// <param name="parameters">The set of parameters that will be used to help locate a specific overload of the specified <paramref name="method"/>.</param>
 84        /// <returns>True if the method matching the name/parameter set is found on the hub, otherwise false.</returns>
 85        public bool TryGetMethod(HubDescriptor hub, string method, out MethodDescriptor descriptor, params IJsonValue[] parameters)
 86        {
 87            string hubMethodKey = BuildHubExecutableMethodCacheKey(hub, method, parameters);
 88
 89            if(!_executableMethods.TryGetValue(hubMethodKey, out descriptor))
 90            {
 91                IEnumerable<MethodDescriptor> overloads;
 92
 93                if(FetchMethodsFor(hub).TryGetValue(method, out overloads))
 94                {
 95                    var matches = overloads.Where(o => o.Matches(parameters)).ToList();
 96
 97                    // If only one match is found, that is the "executable" version, otherwise none of the methods can be returned because we don't know which one was actually being targeted
 98                    descriptor =  matches.Count == 1 ? matches[0] : null;
 99                }
100                else
101                {
102                    descriptor = null;
103                }
104
105                // If an executable method was found, cache it for future lookups (NOTE: we don't cache null instances because it could be a surface area for DoS attack by supplying random method names to flood the cache)
106                if(descriptor != null)
107                {
108                    _executableMethods.TryAdd(hubMethodKey, descriptor);
109                }
110            }
111
112            return descriptor != null;
113        }
114
115        private static string BuildHubExecutableMethodCacheKey(HubDescriptor hub, string method, IJsonValue[] parameters)
116        {
117            string normalizedParameterCountKeyPart;
118
119            if(parameters != null)
120            {
121                normalizedParameterCountKeyPart = parameters.Length.ToString(CultureInfo.InvariantCulture);
122            }
123            else
124            {
125                // NOTE: we normailize a null parameter array to be the same as an empty (i.e. Length == 0) parameter array
126                normalizedParameterCountKeyPart = "0";
127            }
128
129            // NOTE: we always normalize to all uppercase since method names are case insensitive and could theoretically come in diff. variations per call
130            string normalizedMethodName = method.ToUpperInvariant();
131            
132            string methodKey = hub.Name + "::" + normalizedMethodName + "(" + normalizedParameterCountKeyPart + ")";
133            
134            return methodKey;
135        }
136
137        private static string GetMethodName(MethodInfo method)
138        {
139            return ReflectionHelper.GetAttributeValue<HubMethodNameAttribute, string>(method, a => a.MethodName)
140                   ?? method.Name;
141        }
142    }
143}