Exemplo n.º 1
0
    private MethodDefinition GenerateFactoryMethod(TypeDefinition targetType, int index, ICodeGenerator codeGenerator)
    {
        if (!targetType.CanMapType())
        {
            return(null);
        }

        MethodDefinition targetTypeCtor = targetType.GetMappingConstructor();

        if (targetTypeCtor == null)
        {
            return(null);
        }

        var factory = new MethodDefinition($"<{targetType.Name}>_generated_{index}",
                                           MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static,
                                           ModuleDefinition.ImportReference(targetType));

        factory.Parameters.Add(new ParameterDefinition("serviceProvider", ParameterAttributes.None, Import.System.IServiceProvider));

        ILProcessor factoryProcessor = factory.Body.GetILProcessor();

        MethodReference getServiceMethod = Import.DependencyInjection.ServiceProviderServiceExtensions_GetService;

        foreach (ParameterDefinition parameter in targetTypeCtor.Parameters)
        {
            factoryProcessor.Emit(OpCodes.Ldarg_0);
            var genericGetService = new GenericInstanceMethod(getServiceMethod);
            genericGetService.GenericArguments.Add(ModuleDefinition.ImportReference(parameter.ParameterType));
            factoryProcessor.Emit(OpCodes.Call, genericGetService);
        }

        factoryProcessor.Emit(OpCodes.Newobj, ModuleDefinition.ImportReference(targetTypeCtor));
        factoryProcessor.Emit(OpCodes.Ret);

        IMethodGenerator methodGenerator = codeGenerator?.Method(factory);

        if (methodGenerator != null)
        {
            var parameters = string.Join(", ", targetTypeCtor.Parameters.Select(x => $"serviceProvider.GetService<{x.ParameterType.NameCSharp(true)}>()"));
            methodGenerator.Append($"return new {targetType.NameCSharp(true)}({parameters});", factoryProcessor.Body.Instructions.First());
            methodGenerator.Append(Environment.NewLine);
        }
        return(factory);
    }
Exemplo n.º 2
0
    private MethodDefinition GenerateAddServicesMethod(Mapping mapping, Settings settings, TypeDefinition containerType, ICodeGenerator codeGenerator)
    {
        var method = new MethodDefinition("AddServices",
                                          MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static,
                                          ModuleDefinition.ImportReference(typeof(void)));

        var serviceCollection = new ParameterDefinition("collection", ParameterAttributes.None, Import.DependencyInjection.IServiceCollection);

        method.Parameters.Add(serviceCollection);

        IMethodGenerator methodGenerator = codeGenerator?.Method(method);
        ILProcessor      processor       = method.Body.GetILProcessor();

        VariableDefinition exceptionList = null;
        VariableDefinition exception     = null;

        if (settings.DebugExceptions)
        {
            var genericType = ModuleDefinition.ImportReference(Import.System.Collections.List.Type.MakeGenericInstanceType(Import.System.Exception));
            exceptionList = new VariableDefinition(genericType);
            exception     = new VariableDefinition(Import.System.Exception);

            method.Body.Variables.Add(exceptionList);
            method.Body.Variables.Add(exception);

            MethodReference listCtor = Import.System.Collections.List.Ctor;
            listCtor = listCtor.MakeGenericDeclaringType(Import.System.Exception);

            Instruction createListInstruction = Instruction.Create(OpCodes.Newobj, listCtor);
            processor.Append(createListInstruction);
            processor.Emit(OpCodes.Stloc, exceptionList);

            methodGenerator?.Append("List<Exception> list = new List<Exception>();", createListInstruction);
            methodGenerator?.Append(Environment.NewLine);
        }

        MethodReference funcCtor = Import.System.Func2_Ctor;

        if (mapping != null)
        {
            int factoryIndex   = 0;
            var factoryMethods = new Dictionary <string, MethodDefinition>();

            foreach (Registration registration in mapping)
            {
                try
                {
                    Logger.Debug($"Processing map for {registration.TargetType.FullName}", AutoDI.DebugLogLevel.Verbose);

                    if (!factoryMethods.TryGetValue(registration.TargetType.FullName,
                                                    out MethodDefinition factoryMethod))
                    {
                        factoryMethod = GenerateFactoryMethod(registration.TargetType, factoryIndex, codeGenerator);
                        if (factoryMethod == null)
                        {
                            Logger.Debug($"No acceptable constructor for '{registration.TargetType.FullName}', skipping map",
                                         AutoDI.DebugLogLevel.Verbose);
                            continue;
                        }
                        factoryMethods[registration.TargetType.FullName] = factoryMethod;
                        factoryIndex++;
                        containerType.Methods.Add(factoryMethod);
                    }


                    var tryStart = Instruction.Create(OpCodes.Ldarg_0); //collection parameter
                    processor.Append(tryStart);

                    TypeReference importedKey = ModuleDefinition.ImportReference(registration.Key);
                    Logger.Debug(
                        $"Mapping {importedKey.FullName} => {registration.TargetType.FullName} ({registration.Lifetime})",
                        AutoDI.DebugLogLevel.Default);
                    processor.Emit(OpCodes.Ldtoken, importedKey);
                    processor.Emit(OpCodes.Call, Import.System.Type.GetTypeFromHandle);

                    processor.Emit(OpCodes.Ldtoken, ModuleDefinition.ImportReference(registration.TargetType));
                    processor.Emit(OpCodes.Call, Import.System.Type.GetTypeFromHandle);

                    processor.Emit(OpCodes.Ldnull);
                    processor.Emit(OpCodes.Ldftn, factoryMethod);
                    processor.Emit(OpCodes.Newobj,
                                   ModuleDefinition.ImportReference(
                                       funcCtor.MakeGenericDeclaringType(Import.System.IServiceProvider,
                                                                         ModuleDefinition.ImportReference(registration.TargetType))));

                    processor.Emit(OpCodes.Ldc_I4, (int)registration.Lifetime);

                    processor.Emit(OpCodes.Call, Import.AutoDI.ServiceCollectionMixins.AddAutoDIService);
                    processor.Emit(OpCodes.Pop);


                    if (settings.DebugExceptions)
                    {
                        Instruction afterCatch = Instruction.Create(OpCodes.Nop);
                        processor.Emit(OpCodes.Leave_S, afterCatch);

                        Instruction handlerStart = Instruction.Create(OpCodes.Stloc, exception);
                        processor.Append(handlerStart);
                        processor.Emit(OpCodes.Ldloc, exceptionList);
                        processor.Emit(OpCodes.Ldstr, $"Error adding type '{registration.TargetType.FullName}' with key '{registration.Key.FullName}'");
                        processor.Emit(OpCodes.Ldloc, exception);

                        processor.Emit(OpCodes.Newobj, Import.AutoDI.Exceptions.AutoDIException_Ctor);
                        var listAdd = Import.System.Collections.List.Add;
                        listAdd = listAdd.MakeGenericDeclaringType(Import.System.Exception);

                        processor.Emit(OpCodes.Callvirt, listAdd);

                        Instruction handlerEnd = Instruction.Create(OpCodes.Leave_S, afterCatch);
                        processor.Append(handlerEnd);

                        var exceptionHandler =
                            new ExceptionHandler(ExceptionHandlerType.Catch)
                        {
                            CatchType    = Import.System.Exception,
                            TryStart     = tryStart,
                            TryEnd       = handlerStart,
                            HandlerStart = handlerStart,
                            HandlerEnd   = afterCatch,
                        };

                        method.Body.ExceptionHandlers.Add(exceptionHandler);

                        processor.Append(afterCatch);
                        if (methodGenerator != null)
                        {
                            methodGenerator.Append("try" + Environment.NewLine + "{" + Environment.NewLine);
                            methodGenerator.Append($"    {serviceCollection.Name}.{Import.AutoDI.ServiceCollectionMixins.AddAutoDIService.Name}(typeof({importedKey.FullNameCSharp()}), typeof({registration.TargetType.FullNameCSharp()}), new Func<{Import.System.IServiceProvider.NameCSharp()}, {registration.TargetType.FullNameCSharp()}>({factoryMethod.Name}), Lifetime.{registration.Lifetime});", tryStart);
                            methodGenerator.Append(Environment.NewLine + "}" + Environment.NewLine + "catch(Exception innerException)" + Environment.NewLine + "{" + Environment.NewLine);
                            methodGenerator.Append($"    list.{listAdd.Name}(new {Import.AutoDI.Exceptions.AutoDIException_Ctor.DeclaringType.Name}(\"Error adding type '{registration.TargetType.FullName}' with key '{registration.Key.FullName}'\", innerException));", handlerStart);
                            methodGenerator.Append(Environment.NewLine + "}" + Environment.NewLine);
                        }
                    }
                    else if (methodGenerator != null)
                    {
                        methodGenerator.Append($"{serviceCollection.Name}.{Import.AutoDI.ServiceCollectionMixins.AddAutoDIService.Name}(typeof({importedKey.FullNameCSharp()}), typeof({registration.TargetType.FullNameCSharp()}), new Func<{Import.System.IServiceProvider.NameCSharp()}, {registration.TargetType.FullNameCSharp()}>({factoryMethod.Name}), Lifetime.{registration.Lifetime});", tryStart);
                        methodGenerator.Append(Environment.NewLine);
                    }
                }
                catch (MultipleConstructorException e)
                {
                    Logger.Error($"Failed to create map for {registration}\r\n{e}");
                }
                catch (Exception e)
                {
                    Logger.Warning($"Failed to create map for {registration}\r\n{e}");
                }
            }
        }

        Instruction @return = Instruction.Create(OpCodes.Ret);

        if (settings.DebugExceptions)
        {
            Instruction loadList = Instruction.Create(OpCodes.Ldloc, exceptionList);
            processor.Append(loadList);

            var listCount = Import.System.Collections.List.Count;
            listCount = listCount.MakeGenericDeclaringType(Import.System.Exception);
            processor.Emit(OpCodes.Callvirt, listCount);
            processor.Emit(OpCodes.Ldc_I4_0);
            processor.Emit(OpCodes.Cgt);
            processor.Emit(OpCodes.Brfalse_S, @return);

            Instruction ldStr = Instruction.Create(OpCodes.Ldstr, $"Error in {AutoDI.Constants.TypeName}.AddServices() generated method");
            processor.Append(ldStr);
            processor.Emit(OpCodes.Ldloc, exceptionList);

            processor.Emit(OpCodes.Newobj, Import.System.AggregateException_Ctor);
            processor.Emit(OpCodes.Throw);

            if (methodGenerator != null)
            {
                methodGenerator.Append("if (list.Count > 0)", loadList);
                methodGenerator.Append(Environment.NewLine + "{" + Environment.NewLine);
                methodGenerator.Append($"    throw new {Import.System.AggregateException_Ctor.DeclaringType.Name}(\"Error in {AutoDI.Constants.TypeName}.{method.Name}() generated method\", list);", ldStr);
                methodGenerator.Append(Environment.NewLine + "}" + Environment.NewLine);
            }
        }

        processor.Append(@return);

        method.Body.OptimizeMacros();

        return(method);
    }
Exemplo n.º 3
0
    private void ProcessMethod(TypeDefinition type, MethodDefinition method, ICodeGenerator generator)
    {
        List <ParameterDefinition> dependencyParameters = method.Parameters.Where(
            p => p.CustomAttributes.Any(a => a.AttributeType.IsType(Import.AutoDI.DependencyAttributeType))).ToList();

        List <PropertyDefinition> dependencyProperties = method.IsConstructor ?
                                                         type.Properties.Where(p => p.CustomAttributes.Any(a => a.AttributeType.IsType(Import.AutoDI.DependencyAttributeType))).ToList() :
                                                         new List <PropertyDefinition>();

        if (dependencyParameters.Any() || dependencyProperties.Any())
        {
            Logger.Debug($"Processing method '{method.Name}' for '{method.DeclaringType.FullName}'", DebugLogLevel.Verbose);

            var injector = new Injector(method);

            IMethodGenerator methodGenerator = generator?.Method(method);
            foreach (ParameterDefinition parameter in dependencyParameters)
            {
                if (!parameter.IsOptional)
                {
                    Logger.Info(
                        $"Constructor parameter {parameter.ParameterType.Name} {parameter.Name} is marked with {Import.AutoDI.DependencyAttributeType.FullName} but is not an optional parameter. In {type.FullName}.");
                }
                if (parameter.Constant != null)
                {
                    Logger.Warning(
                        $"Constructor parameter {parameter.ParameterType.Name} {parameter.Name} in {type.FullName} does not have a null default value. AutoDI will only resolve dependencies that are null");
                }

                var initInstruction  = Instruction.Create(OpCodes.Ldarg, parameter);
                var storeInstruction = Instruction.Create(OpCodes.Starg, parameter);
                ResolveDependency(parameter.ParameterType, parameter,
                                  new[] { initInstruction },
                                  null,
                                  storeInstruction,
                                  parameter.Name);
            }


            foreach (PropertyDefinition property in dependencyProperties)
            {
                FieldDefinition backingField = null;
                //Store the return from the resolve method in the method parameter
                if (property.SetMethod == null)
                {
                    //NB: Constant string, compiler detail... yuck yuck and double duck
                    backingField = property.DeclaringType.Fields.FirstOrDefault(f => f.Name == $"<{property.Name}>k__BackingField");
                    if (backingField == null)
                    {
                        Logger.Warning(
                            $"{property.FullName} is marked with {Import.AutoDI.DependencyAttributeType.FullName} but cannot be set. Dependency properties must either be auto properties or have a setter");
                        continue;
                    }
                }

                //injector.Insert(OpCodes.Call, property.GetMethod);
                ResolveDependency(property.PropertyType, property,
                                  new[]
                {
                    Instruction.Create(OpCodes.Ldarg_0),
                    Instruction.Create(OpCodes.Call, property.GetMethod),
                },
                                  Instruction.Create(OpCodes.Ldarg_0),
                                  property.SetMethod != null
                        ? Instruction.Create(OpCodes.Call, property.SetMethod)
                        : Instruction.Create(OpCodes.Stfld, backingField),
                                  property.Name);
            }

            methodGenerator?.Append("//We now return you to your regularly scheduled method");

            method.Body.OptimizeMacros();

            void ResolveDependency(TypeReference dependencyType, ICustomAttributeProvider source,
                                   Instruction[] loadSource,
                                   Instruction resolveAssignmentTarget,
                                   Instruction setResult,
                                   string dependencyName)
            {
                //Push dependency parameter onto the stack
                if (methodGenerator != null)
                {
                    methodGenerator.Append($"if ({dependencyName} == null)", loadSource.First());
                    methodGenerator.Append(Environment.NewLine + "{" + Environment.NewLine);
                }

                injector.Insert(loadSource);
                var afterParam = Instruction.Create(OpCodes.Nop);

                //Push null onto the stack
                injector.Insert(OpCodes.Ldnull);
                //Push 1 if the values are equal, 0 if they are not equal
                injector.Insert(OpCodes.Ceq);
                //Branch if the value is false (0), the dependency was set by the caller we wont replace it
                injector.Insert(OpCodes.Brfalse_S, afterParam);
                //Push the dependency resolver onto the stack
                if (resolveAssignmentTarget != null)
                {
                    injector.Insert(resolveAssignmentTarget);
                }

                //Create parameters array
                var dependencyAttribute = source.CustomAttributes.First(x => x.AttributeType.IsType(Import.AutoDI.DependencyAttributeType));
                var values =
                    (dependencyAttribute.ConstructorArguments?.FirstOrDefault().Value as CustomAttributeArgument[])
                    ?.Select(x => x.Value)
                    .OfType <CustomAttributeArgument>()
                    .ToArray();
                //Create array of appropriate length
                Instruction loadArraySize = injector.Insert(OpCodes.Ldc_I4, values?.Length ?? 0);

                if (methodGenerator != null)
                {
                    methodGenerator.Append($"    {dependencyName} = GlobalDI.GetService<{dependencyType.FullNameCSharp()}>();", resolveAssignmentTarget ?? loadArraySize);
                    methodGenerator.Append(Environment.NewLine);
                }

                injector.Insert(OpCodes.Newarr, ModuleDefinition.ImportReference(typeof(object)));
                if (values?.Length > 0)
                {
                    for (int i = 0; i < values.Length; ++i)
                    {
                        injector.Insert(OpCodes.Dup);
                        //Push the array index to insert
                        injector.Insert(OpCodes.Ldc_I4, i);
                        //Insert constant value with any boxing/conversion needed
                        InsertObjectConstant(injector, values[i].Value, values[i].Type.Resolve());
                        //Push the object into the array at index
                        injector.Insert(OpCodes.Stelem_Ref);
                    }
                }

                //Call the resolve method
                var getServiceMethod = new GenericInstanceMethod(Import.AutoDI.GlobalDI.GetService)
                {
                    GenericArguments = { ModuleDefinition.ImportReference(dependencyType) }
                };

                injector.Insert(OpCodes.Call, getServiceMethod);
                //Set the return from the resolve method into the parameter
                injector.Insert(setResult);
                injector.Insert(afterParam);

                if (methodGenerator != null)
                {
                    methodGenerator.Append("}", afterParam);
                    methodGenerator.Append(Environment.NewLine);
                }
            }
        }
    }