Skip to content

Commit

Permalink
Add support for Hub method overloads and optional parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
Xiaohongt committed May 24, 2013
1 parent de6cd74 commit b46a95b
Show file tree
Hide file tree
Showing 10 changed files with 360 additions and 19 deletions.
Expand Up @@ -18,13 +18,38 @@ public static bool Matches(this MethodDescriptor methodDescriptor, IList<IJsonVa
throw new ArgumentNullException("methodDescriptor");
}

if ((methodDescriptor.Parameters.Count > 0 && parameters == null)
|| methodDescriptor.Parameters.Count != parameters.Count)
if (methodDescriptor.Parameters.Count > 0 && parameters == null)
{
return false;
}

if (methodDescriptor.Parameters.Count != parameters.Count)
{
if (methodDescriptor.Parameters.Count < parameters.Count)
{
return false;
}

//if params are optional, we can accept the missing parameters
if (methodDescriptor.Parameters[parameters.Count].IsOptional)
{
return true;
}
else
{
//if the last param is Params Array, then we can accept the missing parameter
if (methodDescriptor.Parameters[parameters.Count].IsParameterArray && (methodDescriptor.Parameters.Count == parameters.Count + 1))
{
return true;
}
}

//no match
return false;
}

return true;
}

}
}
Expand Up @@ -48,7 +48,33 @@ public virtual IList<object> ResolveMethodParameters(MethodDescriptor method, IL
throw new ArgumentNullException("method");
}

return method.Parameters.Zip(values, ResolveParameter).ToArray();
var args = method.Parameters.Zip(values, ResolveParameter).ToList();

//add Type.Missing for Optional parameter
if (method.Parameters.Count != args.Count)
{
for (int i = args.Count; i <= method.Parameters.Count - 1; i++)
{
//if params are optional, we add Type.Missing for each missing parameter
if (method.Parameters[i].IsOptional)
{
//args.Add(Type.Missing);
args.Add(method.Parameters[i].DefaultValue);
}
else
{
//if the last param is Params Array, then we add empty array for the missing parameter
if ((method.Parameters[i].IsParameterArray) && (i == args.Count))
{
args.Add(Array.CreateInstance(method.Parameters[i].ParameterType.GetElementType(), 0));
}

}

}
}

return args.ToArray();
}
}
}
Expand Up @@ -18,6 +18,20 @@ public class ParameterDescriptor
/// Parameter type.
/// </summary>
public virtual Type ParameterType { get; set; }

/// <summary>
/// Parameter IsOptional.
/// </summary>
public virtual bool IsOptional { get; set; }

/// <summary>
/// Parameter DefaultValue.
/// </summary>
public virtual object DefaultValue { get; set; }
/// <summary>
/// Parameter IsParameterArray.
/// </summary>
public virtual bool IsParameterArray { get; set; }
}
}

Expand Up @@ -13,12 +13,12 @@ namespace Microsoft.AspNet.SignalR.Hubs
public class ReflectedMethodDescriptorProvider : IMethodDescriptorProvider
{
private readonly ConcurrentDictionary<string, IDictionary<string, IEnumerable<MethodDescriptor>>> _methods;
private readonly ConcurrentDictionary<string, MethodDescriptor> _executableMethods;
private readonly ConcurrentDictionary<string, IEnumerable<MethodDescriptor>> _executableMethods;

public ReflectedMethodDescriptorProvider()
{
_methods = new ConcurrentDictionary<string, IDictionary<string, IEnumerable<MethodDescriptor>>>(StringComparer.OrdinalIgnoreCase);
_executableMethods = new ConcurrentDictionary<string, MethodDescriptor>(StringComparer.OrdinalIgnoreCase);
_executableMethods = new ConcurrentDictionary<string, IEnumerable<MethodDescriptor>>(StringComparer.OrdinalIgnoreCase);
}

public IEnumerable<MethodDescriptor> GetMethods(HubDescriptor hub)
Expand Down Expand Up @@ -67,6 +67,9 @@ public IEnumerable<MethodDescriptor> GetMethods(HubDescriptor hub)
{
Name = p.Name,
ParameterType = p.ParameterType,
IsOptional = p.IsOptional,
DefaultValue = p.DefaultValue,
IsParameterArray = p.GetCustomAttributes(typeof(ParamArrayAttribute), false).Length > 0,
})
.ToList()
}),
Expand All @@ -88,27 +91,131 @@ public IEnumerable<MethodDescriptor> GetMethods(HubDescriptor hub)
public bool TryGetMethod(HubDescriptor hub, string method, out MethodDescriptor descriptor, IList<IJsonValue> parameters)
{
string hubMethodKey = BuildHubExecutableMethodCacheKey(hub, method, parameters);
IEnumerable<MethodDescriptor> overloads;
descriptor = null;

if (!_executableMethods.TryGetValue(hubMethodKey, out descriptor))
if (!_executableMethods.TryGetValue(hubMethodKey, out overloads))
{
IEnumerable<MethodDescriptor> overloads;

if (FetchMethodsFor(hub).TryGetValue(method, out overloads))
{
var matches = overloads.Where(o => o.Matches(parameters)).ToList();

// 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
descriptor = matches.Count == 1 ? matches[0] : null;
}
else
{
descriptor = null;
if (overloads != null)
{
// If executable method overloads 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)
_executableMethods.TryAdd(hubMethodKey, overloads);
}
}
}

// 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)
if (descriptor != null)
if (overloads != null)
{
var matches = overloads.Where(o => o.Matches(parameters)).ToList();


if (matches.Count == 1)
descriptor = matches[0];

//support overloading, choose the best match to parameters which has less extra parameters in method, and parameter type match.
if (matches.Count > 1)
{
_executableMethods.TryAdd(hubMethodKey, descriptor);

if (parameters.Count > 0)
{
List<MethodDescriptor> paramCanConvertMatches = new List<MethodDescriptor>();

foreach (var match in matches)
{
bool canConvert = true;
for (int i = 0; i < parameters.Count; i++)
{
if (!parameters[i].CanConvertTo(match.Parameters[i].ParameterType))
{
canConvert = false;
}
}

if (canConvert == true)
paramCanConvertMatches.Add(match);
}


//one for parameters type match
if (paramCanConvertMatches.Count == 1)
descriptor = paramCanConvertMatches[0];

if (paramCanConvertMatches.Count > 1)
{
int leastParamsMatch = 0;

//multiple mataches for least paramters in matches, so check parameter type match
for (int i = 0; i < paramCanConvertMatches.Count; i++)
{
if ((paramCanConvertMatches[i].Parameters.Count == parameters.Count))
{
if ((!paramCanConvertMatches[i].Parameters[parameters.Count - 1].IsParameterArray) && (!paramCanConvertMatches[i].Parameters[parameters.Count - 1].IsOptional))
{
leastParamsMatch = i;
descriptor = paramCanConvertMatches[i];
break;
}

if (paramCanConvertMatches[i].Parameters[parameters.Count - 1].IsOptional)
{
leastParamsMatch = i;
}

// Parameter Array match last
if (paramCanConvertMatches[i].Parameters[parameters.Count - 1].IsParameterArray)
{
if (!paramCanConvertMatches[leastParamsMatch].Parameters[parameters.Count - 1].IsOptional)
{
leastParamsMatch = i;
}
}
}

if ((paramCanConvertMatches[i].Parameters.Count > parameters.Count))
{
//multiple mataches for the last parameter in parameters type match, so mataches should have extra parameter
//match the IsOptional for the extra parameter
if (paramCanConvertMatches[i].Parameters[parameters.Count].IsOptional)
{
if (paramCanConvertMatches[i].Parameters.Count < paramCanConvertMatches[leastParamsMatch].Parameters.Count)
{
leastParamsMatch = i;
}
}

// Parameter Array match last
if (paramCanConvertMatches[i].Parameters[parameters.Count].IsParameterArray)
{
if (!paramCanConvertMatches[leastParamsMatch].Parameters[parameters.Count - 1].IsOptional)
{
leastParamsMatch = i;
}
}
}
}

descriptor = paramCanConvertMatches[leastParamsMatch];
}

}
else
{
foreach (var match in matches)
{
//match the IsOptional if parameters count is 0
if (match.Parameters[0].IsOptional)
{
descriptor = match;
break;
}
else
{
descriptor = match;
}
}
}
}
}

Expand Down
8 changes: 8 additions & 0 deletions src/Microsoft.AspNet.SignalR.Core/Json/JRawValue.cs
Expand Up @@ -32,6 +32,14 @@ public object ConvertTo(Type type)
public bool CanConvertTo(Type type)
{
// TODO: Implement when we implement better method overload resolution
try
{
var value = ConvertTo(type);
}
catch
{
return false;
}
return true;
}
}
Expand Down
Expand Up @@ -103,6 +103,7 @@
<Content Include="Tests\FunctionalTests\Common\KeepAliveFacts.js" />
<Content Include="Tests\FunctionalTests\Core\JsonFacts.js" />
<Content Include="Tests\FunctionalTests\Core\NegotiateFacts.js" />
<Content Include="Tests\FunctionalTests\Hubs\HubMethodParamFacts.js" />
<Content Include="Tests\FunctionalTests\Hubs\HubEventHandlerFacts.js" />
<Content Include="Tests\FunctionalTests\Hubs\HubGroupFacts.js" />
<Content Include="Tests\FunctionalTests\Transports\All\ConnectionFacts.js" />
Expand Down

0 comments on commit b46a95b

Please sign in to comment.