using FluentValidation; using Microsoft.Extensions.Logging; using System; using System.Collections.Concurrent; using System.Linq; using System.Linq.Expressions; using System.Threading.Tasks; using zero.Core.Entities; using zero.Core.Options; namespace zero.Core.Collections { public class CollectionInterceptorHandler : ICollectionInterceptorHandler { protected ConcurrentDictionary Interceptors { get; private set; } = new(); protected IZeroOptions Options { get; set; } protected IServiceProvider Services { get; set; } protected ILogger Logger { get; set; } public CollectionInterceptorHandler(IZeroOptions options, IServiceProvider serviceProvider, ILogger logger) { Logger = logger; Options = options; Services = serviceProvider; } /// public InterceptorInstruction Create(string operationName, TParameters parameters) where T : ZeroEntity where TParameters : CollectionInterceptor.Parameters { InterceptorInstruction instruction = new(parameters); instruction.Operation = operationName; instruction.BeforeOperationHandler = async expression => await HandleBefore(expression, instruction); instruction.AfterOperationHandler = async expression => await HandleAfter(expression, instruction); return instruction; } /// /// Calls all matching interceptors with the specified expression /// internal async Task HandleBefore(Expression, Task>>> expression, InterceptorInstruction instruction) where T : ZeroEntity where TParameters : CollectionInterceptor.Parameters { string typeName = (typeof(T)).Name; Func, Task>> func = default; foreach (InterceptorRegistration registration in ForType(typeof(T))) { if (!TryResolve(registration, out ICollectionInterceptor interceptor)) { continue; } if (!interceptor.CanRun(instruction.Parameters)) { continue; } if (func == default) { func = expression.Compile(); } // we do not log save operations as they are always called for update/create which are already logged beforehand if (instruction.Operation != "save") { Logger.LogDebug("Run interceptor {interceptor} for {type}:{operation}", registration.Name, typeName, instruction.Operation); } InterceptorResult result = await func(interceptor); if (result == default) { result = new(); } result.InterceptorHash = registration.Hash; instruction.Results.Add(result); if (result.Result != null) { instruction.EntityResult = result.Result; break; } if (result.Prevent) { break; } } } /// /// Calls all matching interceptors with the specified expression /// internal async Task HandleAfter(Expression, Task>> expression, InterceptorInstruction instruction) where T : ZeroEntity where TParameters : CollectionInterceptor.Parameters { Func, Task> func = default; instruction ??= new(); foreach (InterceptorRegistration registration in ForType(typeof(T))) { if (!TryResolve(registration, out ICollectionInterceptor interceptor)) { continue; } if (!interceptor.CanRun(instruction.Parameters)) { continue; } InterceptorResult beforeResult = instruction?.Results.FirstOrDefault(res => res.InterceptorHash == registration.Hash); if (func == default) { func = expression.Compile(); } await func(interceptor); } } /// /// Get all interceptors for a certain type /// IOrderedEnumerable ForType(Type targetType) { return Options.Interceptors.GetAllItems().Where(x => x.CanHandle(targetType)).OrderByDescending(x => x.Gravity); // return Interceptors.Where(item => item.Types.Count == 0 || item.Types.Any(type => targetType.IsAssignableFrom(type))); } /// /// Resolves an interceptor from the service provider /// bool TryResolve(InterceptorRegistration registration, out ICollectionInterceptor interceptor) where T : ZeroEntity { Type type = registration.InterceptorType; if (Interceptors.TryGetValue(type, out object interceptorObj)) { interceptor = interceptorObj as ICollectionInterceptor; return interceptor != null; } object service = Services.GetService(type); interceptor = service as ICollectionInterceptor; if (interceptor == null && service != null && service is ICollectionInterceptor) { interceptor = new CollectionInterceptorShim(service as ICollectionInterceptor); } if (interceptor == null) { Logger.LogWarning("Could not resolve interceptor {interceptor}", registration.Name); } Interceptors.TryAdd(type, interceptor); return interceptor != null; } } public interface ICollectionInterceptorHandler { /// /// Creates a new interceptor instruction /// InterceptorInstruction Create(string operationName, TParameters parameters) where T : ZeroEntity where TParameters : CollectionInterceptor.Parameters; } }