9using System.Collections.Concurrent;
10using System.Collections.Generic;
12using System.Reflection;
13using System.Threading.Tasks;
15using System.Runtime.Loader;
29 private static readonly BindingFlags _bindingAttr = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly | BindingFlags.DeclaredOnly;
30 private static readonly BindingFlags _factoryBindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly;
33 private static readonly ConcurrentDictionary<string, Tuple<string, ServiceProviderMethodInfo>> _methodCache =
34 new ConcurrentDictionary<string, Tuple<string, ServiceProviderMethodInfo>>();
36 private static readonly ConcurrentDictionary<string, ServiceProviderMethodInfo> _methodCache =
37 new ConcurrentDictionary<string, ServiceProviderMethodInfo>();
54 throw new ArgumentNullException(nameof(target));
56 var targetType = target.GetType();
71 if (targetType ==
null)
72 throw new ArgumentNullException(nameof(targetType));
74 var typeOfOperation = typeof(T);
76 var cacheKey = GetCacheKeyName(targetType, typeOfOperation, criteria);
79 if (_methodCache.TryGetValue(cacheKey, out var unloadableCachedMethodInfo))
81 var cachedMethod = unloadableCachedMethodInfo?.Item2;
87 if (!throwOnError || cachedMethod !=
null)
91 var candidates =
new List<ScoredMethodInfo>();
93 if (factoryInfo !=
null)
95 var factoryLoader =
ApplicationContext.CurrentServiceProvider.GetService(typeof(Server.IObjectFactoryLoader)) as Server.IObjectFactoryLoader;
96 var factoryType = factoryLoader?.GetFactoryType(factoryInfo.FactoryTypeName);
97 var ftList =
new List<System.Reflection.MethodInfo>();
99 while (factoryType !=
null)
103 ftList.AddRange(factoryType.GetMethods(_factoryBindingAttr).Where(m => m.Name == factoryInfo.CreateMethodName));
105 ftList.AddRange(factoryType.GetMethods(_factoryBindingAttr).Where(m => m.Name == factoryInfo.FetchMethodName));
107 ftList.AddRange(factoryType.GetMethods(_factoryBindingAttr).Where(m => m.Name == factoryInfo.DeleteMethodName));
109 ftList.AddRange(factoryType.GetMethods(_factoryBindingAttr).Where(m => m.Name == factoryInfo.ExecuteMethodName));
111 ftList.AddRange(factoryType.GetMethods(_factoryBindingAttr).Where(m => m.Name ==
"Child_Create"));
113 ftList.AddRange(factoryType.GetMethods(_factoryBindingAttr).Where(m => m.Name == factoryInfo.UpdateMethodName));
114 factoryType = factoryType.BaseType;
115 candidates.AddRange(ftList.Select(r =>
new ScoredMethodInfo { MethodInfo = r, Score = level }));
120 var ftlist = targetType.GetMethods(_bindingAttr).Where(m => m.Name ==
"Child_Create");
121 candidates.AddRange(ftlist.Select(r =>
new ScoredMethodInfo { MethodInfo = r, Score = 0 }));
130 var ttList = tt.GetMethods(_bindingAttr).Where(m => m.GetCustomAttributes<T>().Any());
131 candidates.AddRange(ttList.Select(r =>
new ScoredMethodInfo { MethodInfo = r, Score = level }));
137 if (!candidates.Any())
139 var attributeName = typeOfOperation.Name.Substring(0, typeOfOperation.Name.IndexOf(
"Attribute"));
140 var methodName = attributeName.Contains(
"Child") ?
141 "Child_" + attributeName.Substring(0, attributeName.IndexOf(
"Child")) :
142 "DataPortal_" + attributeName;
147 var ttList = tt.GetMethods(_bindingAttr).Where(m => m.Name == methodName);
148 candidates.AddRange(ttList.Select(r =>
new ScoredMethodInfo { MethodInfo = r, Score = level }));
155 ScoredMethodInfo result =
null;
157 if (candidates !=
null && candidates.Any())
160 int criteriaLength = 0;
161 if (criteria !=
null)
162 if (criteria.GetType().Equals(typeof(
object[])))
163 criteriaLength = criteria.GetLength(0);
167 var matches =
new List<ScoredMethodInfo>();
168 if (criteriaLength > 0)
170 foreach (var item
in candidates)
173 var methodParams = GetCriteriaParameters(item.MethodInfo);
174 if (methodParams.Length == criteriaLength)
177 if (criteria.GetType().Equals(typeof(
object[])))
179 foreach (var c
in criteria)
181 var currentScore = CalculateParameterScore(methodParams[index], c);
182 if (currentScore == 0)
187 score += currentScore;
193 var currentScore = CalculateParameterScore(methodParams[index], criteria);
194 if (currentScore != 0)
196 score += currentScore;
201 if (index == criteriaLength)
205 if (matches.Count() == 0 &&
210 foreach (var item
in candidates)
212 if (GetCriteriaParameters(item.MethodInfo).Length == 0)
219 foreach (var item
in candidates)
221 if (GetCriteriaParameters(item.MethodInfo).Length == 0)
225 if (matches.Count == 0)
228 foreach (var item
in candidates)
230 var lastParam = item.MethodInfo.GetParameters().LastOrDefault();
231 if (lastParam !=
null && lastParam.ParameterType.Equals(typeof(
object[])) &&
232 lastParam.GetCustomAttributes<ParamArrayAttribute>().Any())
239 if (matches.Count > 0)
243 if (matches.Count > 1)
247 foreach (var item
in matches)
248 item.Score += GetDIParameters(item.MethodInfo).Length;
250 var maxScore =
int.MinValue;
252 foreach (var item
in matches)
254 if (item.Score > maxScore)
256 maxScore = item.Score;
260 else if (item.Score == maxScore)
269 throw new AmbiguousMatchException($
"{targetType.FullName}.[{typeOfOperation.Name.Replace("Attribute
", "")}]{GetCriteriaTypeNames(criteria)}. Matches: {string.Join(",
", matches.Select(m => $"{m.MethodInfo.DeclaringType.FullName}[{m.MethodInfo}]
"))}");
273 _methodCache.TryAdd(cacheKey,
null);
289 var baseType = targetType.BaseType;
290 if (baseType ==
null)
293 throw new TargetParameterCountException(cacheKey);
296 _methodCache.TryAdd(cacheKey,
null);
305 catch (TargetParameterCountException ex)
307 throw new TargetParameterCountException(cacheKey, ex);
309 catch (AmbiguousMatchException ex)
311 throw new AmbiguousMatchException($
"{targetType.FullName}.[{typeOfOperation.Name.Replace("Attribute
", "")}]{GetCriteriaTypeNames(criteria)}.", ex);
315 if (resultingMethod !=
null)
318 var cacheInstance = AssemblyLoadContextManager.CreateCacheInstance(targetType, resultingMethod, OnAssemblyLoadContextUnload);
319 _ = _methodCache.TryAdd(cacheKey, cacheInstance);
321 _methodCache.TryAdd(cacheKey, resultingMethod);
324 return resultingMethod;
327 private static int CalculateParameterScore(ParameterInfo methodParam,
object c)
331 if (methodParam.ParameterType.IsPrimitive)
333 else if (methodParam.ParameterType == typeof(
object))
335 else if (methodParam.ParameterType == typeof(
object[]))
337 else if (methodParam.ParameterType.IsClass)
339 else if (methodParam.ParameterType.IsArray)
341 else if (methodParam.ParameterType.IsInterface)
343 else if (Nullable.GetUnderlyingType(methodParam.ParameterType) !=
null)
348 if (c.GetType() == methodParam.ParameterType)
350 else if (methodParam.ParameterType.Equals(typeof(
object)))
352 else if (methodParam.ParameterType.IsAssignableFrom(c.GetType()))
359 private static string GetCacheKeyName(Type targetType, Type operationType,
object[] criteria)
361 return $
"{targetType.FullName}.[{operationType.Name.Replace("Attribute
", "")}]{GetCriteriaTypeNames(criteria)}";
364 private static string GetCriteriaTypeNames(
object[] criteria)
366 var result =
new System.Text.StringBuilder();
368 if (criteria !=
null)
370 if (criteria.GetType().Equals(typeof(
object[])))
373 foreach (var item
in criteria)
380 result.Append(
"null");
382 result.Append(GetTypeName(item.GetType()));
386 result.Append(GetTypeName(criteria.GetType()));
390 return result.ToString();
393 private static string GetTypeName(Type type)
397 return $
"{GetTypeName(type.GetElementType())}[]";
400 if (!type.IsGenericType)
405 var result =
new System.Text.StringBuilder();
406 var genericArguments = type.GetGenericArguments();
407 result.Append(type.Name);
410 for (
int i = 0; i < genericArguments.Length; i++)
417 result.Append(GetTypeName(genericArguments[i]));
422 return result.ToString();
425 private static ParameterInfo[] GetCriteriaParameters(System.Reflection.MethodInfo method)
427 var result =
new List<ParameterInfo>();
428 foreach (var item
in method.GetParameters())
433 return result.ToArray();
436 private static ParameterInfo[] GetDIParameters(System.Reflection.MethodInfo method)
438 var result =
new List<ParameterInfo>();
439 foreach (var item
in method.GetParameters())
444 return result.ToArray();
467 plist =
new object[] { parameters };
473 int criteriaIndex = 0;
481 throw new NullReferenceException(nameof(service));
483 plist[index] = service.GetService(item.ParameterType);
488 if (parameters.GetType().Equals(typeof(
object[])))
490 if (parameters ==
null || parameters.Length - 1 < criteriaIndex)
493 plist[index] = parameters[criteriaIndex];
496 plist[index] = parameters;
507 await ((Task)method.
DynamicMethod(obj, plist)).ConfigureAwait(
false);
512 return await ((Task<object>)method.
DynamicMethod(obj, plist)).ConfigureAwait(
false);
522 Exception inner =
null;
523 if (ex.InnerException ==
null)
526 inner = ex.InnerException;
532 private static void OnAssemblyLoadContextUnload(AssemblyLoadContext context)
534 AssemblyLoadContextManager.RemoveFromCache(_methodCache, context,
true);
538 private class ScoredMethodInfo
540 public int Score {
get;
set; }
541 public System.Reflection.MethodInfo MethodInfo {
get;
set; }
Provides consistent context information between the client and server DataPortal objects.
ApplicationContext(ApplicationContextAccessor applicationContextAccessor)
Creates a new instance of the type
Specifies a method used by the server-side data portal to initialize a new domain object.
Specifies a method used by the server-side data portal to initialize a new child object.
Base type for data portal operation attributes.
Specifies a method used by the server-side data portal to delete domain object data during an update ...
Specifies a method used by the server-side data portal to delete domain object data during an explici...
Specifies a method used by the server-side data portal to delete child object data during an update o...
Specifies a method used by the server-side data portal to execute a command object.
Specifies a method used by the server-side data portal to load existing data into the domain object.
Specifies a parameter that is provided via dependency injection.
Maintains metadata about a method.
MethodInfo(string name)
Creates an instance of the type.
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.
This exception is returned from the CallMethod method in the server-side DataPortal and contains the ...
Methods to dynamically find/invoke methods with data portal and DI provided params
async Task< object > CallMethodTryAsync(object obj, ServiceProviderMethodInfo method, object[] parameters)
Invoke a method async if possible, providing parameters from the params array and from DI
ServiceProviderMethodInfo FindDataPortalMethod< T >(object target, object[] criteria)
Find a method based on data portal criteria and providing any remaining parameters with values from a...
Class that contains cached metadata about data portal method to be invoked
System.Reflection.MethodInfo MethodInfo
Gets or sets the MethodInfo object for the method
bool IsAsyncTaskObject
Gets a value indicating whether the method returns a Task of T
bool TakesParamArray
Gets a value indicating whether the method takes a param array as its parameter
void PrepForInvocation()
Initializes and caches the metastate values necessary to invoke the method
DynamicMethodDelegate DynamicMethod
Gets delegate representing an expression that can invoke the method
bool[] IsInjected
Gets an array of values indicating which parameters need to be injected
ParameterInfo[] Parameters
Gets the parameters for the method
bool IsAsyncTask
Gets a value indicating whether the method returns type Task
Specifies that the data portal should invoke a factory object rather than the business object.
Specifies a method used by the server-side data portal to update child object data during an update o...