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); }
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); }
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); } } } }