9using System.Collections.Concurrent;
10using System.Collections.Generic;
12using System.Reflection;
13using System.Threading.Tasks;
22 public static class ServiceProviderMethodCaller
24 private static readonly BindingFlags _bindingAttr = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly | BindingFlags.DeclaredOnly;
25 private static readonly BindingFlags _factoryBindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly;
26 private static readonly ConcurrentDictionary<string, ServiceProviderMethodInfo> _methodCache =
27 new ConcurrentDictionary<string, ServiceProviderMethodInfo>();
36 public static ServiceProviderMethodInfo FindDataPortalMethod<T>(
object target,
object[] criteria)
37 where T : DataPortalOperationAttribute
40 throw new ArgumentNullException(nameof(target));
42 var targetType = target.GetType();
43 return FindDataPortalMethod<T>(targetType, criteria);
54 public static ServiceProviderMethodInfo FindDataPortalMethod<T>(Type targetType,
object[] criteria,
bool throwOnError =
true)
55 where T : DataPortalOperationAttribute
57 if (targetType ==
null)
58 throw new ArgumentNullException(nameof(targetType));
60 var typeOfOperation = typeof(T);
62 if (typeOfOperation == typeof(DeleteSelfAttribute) || typeOfOperation == typeof(DeleteSelfChildAttribute))
64 criteria = Array.Empty<
object>();
67 var cacheKey = GetCacheKeyName(targetType, typeOfOperation, criteria);
68 if (_methodCache.TryGetValue(cacheKey, out ServiceProviderMethodInfo cachedMethod))
69 if (!throwOnError || cachedMethod !=
null)
72 var candidates =
new List<ScoredMethodInfo>();
74 if (factoryInfo !=
null)
77 var ftList =
new List<System.Reflection.MethodInfo>();
79 while (factoryType !=
null)
82 if (typeOfOperation == typeof(CreateAttribute))
83 ftList.AddRange(factoryType.GetMethods(_factoryBindingAttr).Where(m => m.Name == factoryInfo.CreateMethodName));
84 else if (typeOfOperation == typeof(FetchAttribute))
85 ftList.AddRange(factoryType.GetMethods(_factoryBindingAttr).Where(m => m.Name == factoryInfo.FetchMethodName));
86 else if (typeOfOperation == typeof(DeleteAttribute))
87 ftList.AddRange(factoryType.GetMethods(_factoryBindingAttr).Where(m => m.Name == factoryInfo.DeleteMethodName));
88 else if (typeOfOperation == typeof(ExecuteAttribute))
89 ftList.AddRange(factoryType.GetMethods(_factoryBindingAttr).Where(m => m.Name == factoryInfo.ExecuteMethodName));
90 else if (typeOfOperation == typeof(CreateChildAttribute))
91 ftList.AddRange(factoryType.GetMethods(_factoryBindingAttr).Where(m => m.Name ==
"Child_Create"));
93 ftList.AddRange(factoryType.GetMethods(_factoryBindingAttr).Where(m => m.Name == factoryInfo.UpdateMethodName));
94 factoryType = factoryType.BaseType;
95 candidates.AddRange(ftList.Select(r =>
new ScoredMethodInfo { MethodInfo = r, Score = level }));
98 if (!candidates.Any() && typeOfOperation == typeof(CreateChildAttribute))
100 var ftlist = targetType.GetMethods(_bindingAttr).Where(m => m.Name ==
"Child_Create");
101 candidates.AddRange(ftList.Select(r =>
new ScoredMethodInfo { MethodInfo = r, Score = 0 }));
110 var ttList = tt.GetMethods(_bindingAttr).Where(m => m.GetCustomAttributes<T>().Any());
111 candidates.AddRange(ttList.Select(r =>
new ScoredMethodInfo { MethodInfo = r, Score = level }));
117 if (!candidates.Any())
119 var attributeName = typeOfOperation.Name.Substring(0, typeOfOperation.Name.IndexOf(
"Attribute"));
120 var methodName = attributeName.Contains(
"Child") ?
121 "Child_" + attributeName.Substring(0, attributeName.IndexOf(
"Child")) :
122 "DataPortal_" + attributeName;
127 var ttList = tt.GetMethods(_bindingAttr).Where(m => m.Name == methodName);
128 candidates.AddRange(ttList.Select(r =>
new ScoredMethodInfo { MethodInfo = r, Score = level }));
135 ScoredMethodInfo result =
null;
137 if (candidates !=
null && candidates.Any())
140 int criteriaLength = 0;
141 if (criteria !=
null)
142 if (criteria.GetType().Equals(typeof(
object[])))
143 criteriaLength = criteria.GetLength(0);
147 var matches =
new List<ScoredMethodInfo>();
148 if (criteriaLength > 0)
150 foreach (var item
in candidates)
153 var methodParams = GetCriteriaParameters(item.MethodInfo);
154 if (methodParams.Length == criteriaLength)
157 if (criteria.GetType().Equals(typeof(
object[])))
159 foreach (var c
in criteria)
161 var currentScore = CalculateParameterScore(methodParams[index], c);
162 if (currentScore == 0)
167 score += currentScore;
173 var currentScore = CalculateParameterScore(methodParams[index], criteria);
174 if (currentScore != 0)
176 score += currentScore;
181 if (index == criteriaLength)
182 matches.Add(
new ScoredMethodInfo { MethodInfo = item.MethodInfo, Score = score + item.Score });
188 foreach (var item
in candidates)
190 if (GetCriteriaParameters(item.MethodInfo).Length == 0)
191 matches.Add(
new ScoredMethodInfo { MethodInfo = item.MethodInfo, Score = item.Score });
194 if (matches.Count == 0)
197 foreach (var item
in candidates)
199 var lastParam = item.MethodInfo.GetParameters().LastOrDefault();
200 if (lastParam !=
null && lastParam.ParameterType.Equals(typeof(
object[])) &&
201 lastParam.GetCustomAttributes<ParamArrayAttribute>().Any())
203 matches.Add(
new ScoredMethodInfo { MethodInfo = item.MethodInfo, Score = 1 + item.Score });
208 if (matches.Count > 0)
212 if (matches.Count > 1)
216 foreach (var item
in matches)
217 item.Score += GetDIParameters(item.MethodInfo).Length;
219 var maxScore =
int.MinValue;
221 foreach (var item
in matches)
223 if (item.Score > maxScore)
225 maxScore = item.Score;
229 else if (item.Score == maxScore)
238 throw new AmbiguousMatchException($
"{targetType.FullName}.[{typeOfOperation.Name.Replace("Attribute
", "")}]{GetCriteriaTypeNames(criteria)}. Matches: {string.Join(",
", matches.Select(m => $"{m.MethodInfo.DeclaringType.FullName}[{m.MethodInfo}]
"))}");
242 _methodCache.TryAdd(cacheKey,
null);
250 ServiceProviderMethodInfo resultingMethod =
null;
253 resultingMethod =
new ServiceProviderMethodInfo { MethodInfo = result.MethodInfo };
257 var baseType = targetType.BaseType;
258 if (baseType ==
null)
261 throw new TargetParameterCountException(cacheKey);
264 _methodCache.TryAdd(cacheKey,
null);
270 resultingMethod = FindDataPortalMethod<T>(baseType, criteria, throwOnError);
272 catch (TargetParameterCountException ex)
274 throw new TargetParameterCountException(cacheKey, ex);
276 catch (AmbiguousMatchException ex)
278 throw new AmbiguousMatchException($
"{targetType.FullName}.[{typeOfOperation.Name.Replace("Attribute
", "")}]{GetCriteriaTypeNames(criteria)}.", ex);
281 _methodCache.TryAdd(cacheKey, resultingMethod);
282 return resultingMethod;
285 private static int CalculateParameterScore(ParameterInfo methodParam,
object c)
289 if (methodParam.ParameterType.IsPrimitive)
291 else if (methodParam.ParameterType == typeof(
object))
293 else if (methodParam.ParameterType == typeof(
object[]))
295 else if (methodParam.ParameterType.IsClass)
297 else if (methodParam.ParameterType.IsArray)
299 else if (methodParam.ParameterType.IsInterface)
301 else if (Nullable.GetUnderlyingType(methodParam.ParameterType) !=
null)
306 if (c.GetType() == methodParam.ParameterType)
308 else if (methodParam.ParameterType.Equals(typeof(
object)))
310 else if (methodParam.ParameterType.IsAssignableFrom(c.GetType()))
317 private static string GetCacheKeyName(Type targetType, Type operationType,
object[] criteria)
319 return $
"{targetType.FullName}.[{operationType.Name.Replace("Attribute
", "")}]{GetCriteriaTypeNames(criteria)}";
322 private static string GetCriteriaTypeNames(
object[] criteria)
324 var result =
new System.Text.StringBuilder();
326 if (criteria !=
null)
328 if (criteria.GetType().Equals(typeof(
object[])))
331 foreach (var item
in criteria)
338 result.Append(
"null");
340 result.Append(GetTypeName(item.GetType()));
344 result.Append(GetTypeName(criteria.GetType()));
348 return result.ToString();
351 private static string GetTypeName(Type type)
355 return $
"{GetTypeName(type.GetElementType())}[]";
358 if (!type.IsGenericType)
363 var result =
new System.Text.StringBuilder();
364 var genericArguments = type.GetGenericArguments();
365 result.Append(type.Name);
368 for (
int i = 0; i < genericArguments.Length; i++)
375 result.Append(GetTypeName(genericArguments[i]));
380 return result.ToString();
383 private static ParameterInfo[] GetCriteriaParameters(System.Reflection.MethodInfo method)
385 var result =
new List<ParameterInfo>();
386 foreach (var item
in method.GetParameters())
388 if (!item.GetCustomAttributes<InjectAttribute>().Any())
391 return result.ToArray();
394 private static ParameterInfo[] GetDIParameters(System.Reflection.MethodInfo method)
396 var result =
new List<ParameterInfo>();
397 foreach (var item
in method.GetParameters())
399 if (item.GetCustomAttributes<InjectAttribute>().Any())
402 return result.ToArray();
413 public static async Task<object> CallMethodTryAsync(
object obj, ServiceProviderMethodInfo method,
object[] parameters)
418 var info = method.MethodInfo;
419 method.PrepForInvocation();
423 if (method.TakesParamArray)
425 plist =
new object[] { parameters };
429 plist =
new object[method.Parameters.Length];
431 int criteriaIndex = 0;
432 var service = ApplicationContext.CurrentServiceProvider;
433 foreach (var item
in method.Parameters)
435 if (method.IsInjected[index])
439 throw new NullReferenceException(nameof(service));
441 plist[index] = service.GetService(item.ParameterType);
446 if (parameters.GetType().Equals(typeof(
object[])))
448 if (parameters ==
null || parameters.Length - 1 < criteriaIndex)
451 plist[index] = parameters[criteriaIndex];
454 plist[index] = parameters;
463 if (method.IsAsyncTask)
465 await ((Task)method.DynamicMethod(obj, plist)).ConfigureAwait(
false);
468 else if (method.IsAsyncTaskObject)
470 return await ((Task<object>)method.DynamicMethod(obj, plist)).ConfigureAwait(
false);
474 var result = method.DynamicMethod(obj, plist);
480 Exception inner =
null;
481 if (ex.InnerException ==
null)
484 inner = ex.InnerException;
489 private class ScoredMethodInfo
491 public int Score {
get;
set; }
492 public System.Reflection.MethodInfo MethodInfo {
get;
set; }
A strongly-typed resource class, for looking up localized strings, etc.
static string MethodNotImplemented
Looks up a localized string similar to not implemented.
static string MethodCallFailed
Looks up a localized string similar to method call failed.
Server-side data portal implementation that invokes an object factory rather than directly interactin...
static IObjectFactoryLoader FactoryLoader
Gets or sets a delegate reference to the method called to create instances of factory objects as requ...
Specifies that the data portal should invoke a factory object rather than the business object.
Type GetFactoryType(string factoryName)
Returns the type of the factory object.