Element autoServiceCustom¶
The XML configuration file has an element autoServiceCustom that can appear under “iocConfiguration/dependencyInjection/autoGeneratedServices” and “iocConfiguration/pluginsSetup/pluginSetup/dependencyInjection/autoGeneratedServices” elements.
The autoServiceCustom element specifies the interface that needs to be auto-implemented and has a child element autoServiceCodeGenerator that specifies an implementation of interface IoC.Configuration.ConfigurationFile.ICustomAutoServiceCodeGenerator.
The implementation of interface IoC.Configuration.ConfigurationFile.ICustomAutoServiceCodeGenerator is responsible for validating the auto-generated interface and for providing a code for the auto-generating the interface implementation.
See the example below or in test project for more details.
Below is the declaration of IoC.Configuration.ConfigurationFile.ICustomAutoServiceCodeGenerator.
Click to expand IoC.Configuration.ConfigurationFile.ICustomAutoServiceCodeGenerator
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | // Copyright (c) IoC.Configuration Project. All rights reserved.
// Licensed under the MIT License. See LICENSE in the solution root for license information.
using JetBrains.Annotations;
using OROptimizer.DynamicCode;
using OROptimizer.ServiceResolver;
using System;
namespace IoC.Configuration.ConfigurationFile
{
/// <summary>
/// Generates an implementation class for interfaces specified in autoServiceCustom element in configuration file.
/// </summary>
public interface ICustomAutoServiceCodeGenerator
{
/// <summary>
/// Validates the configuration of "autoServiceCustom" element.
/// Throws an exception if the configuration is invalid.
/// </summary>
/// <param name="customAutoGeneratedServiceInfo"></param>
void Validate([NotNull] ICustomAutoGeneratedServiceInfo customAutoGeneratedServiceInfo);
/// <summary>
/// Generates a C# code for the auto-implemented interface specified in "autoServiceCustom" element.
/// </summary>
/// <param name="customAutoGeneratedServiceInfo">Configuration of auto-implemented service configuration.</param>
/// <param name="dynamicAssemblyBuilder">
/// An instance of <see cref="IDynamicAssemblyBuilder"/>.
/// Use methods <see cref="IDynamicAssemblyBuilder.StartDynamicallyGeneratedClass(string, string)"/>,
/// <see cref="IDynamicAssemblyBuilder.StartDynamicallyGeneratedClass(string, System.Collections.Generic.IEnumerable{string}, string)"/>,
/// <see cref="IDynamicAssemblyBuilder.AddCSharpFile(string)"/>, etc., to generate the C# code for the implementation.
/// </param>
/// <param name="generatedClassNamespace">The generated class namespace.</param>
/// <param name="generatedClassName">The generated class name without namespace.</param>
void GenerateCSharp([NotNull] ICustomAutoGeneratedServiceInfo customAutoGeneratedServiceInfo,
[NotNull] IDynamicAssemblyBuilder dynamicAssemblyBuilder,
[NotNull] string generatedClassNamespace, [NotNull] string generatedClassName);
/// <summary>
/// Validates the configuration of "autoServiceCustom" element after the IoC container is loaded.
/// Throws an exception if the configuration is invalid.
/// </summary>
/// <param name="diContainer">The IoC container. Use IDiContainer.<see cref="IServiceResolver.Resolve(Type)"/> to resolve types.</param>
/// <param name="customAutoGeneratedServiceInfo">Information about auto-implemented interface.</param>
void ValidateOnIoCContainerLoaded([NotNull] DiContainer.IDiContainer diContainer, [NotNull] ICustomAutoGeneratedServiceInfo customAutoGeneratedServiceInfo);
}
}
|
The motivation for adding autoServiceCustom was to provide an auto-implemented interface implementation based on C# attributes applied to the interface and in interface and interface methods and properties.
For example one such scenario when autoServiceCustom might be handy is when we want to auto-implement interfaces for object relational mapping, when interfaces represent database tables, and are decorated with metadata attributes that describe the database schema.
In this case, an implementation of IoC.Configuration.ConfigurationFile.ICustomAutoServiceCodeGenerator can scan the attributes applied to an interface and interface methods and properties, and generate an implementation of an interface based on metadata attributes.
The tests in IoC.Configuration.Tests.AutoServiceCustom.AutoServiceCustomSuccessfulLoadTests.cs demonstrate an example of auto-generated repository interface implementations based on attributes applied to the interface.
Note
Refer to IoCConfiguration_autoServiceCustom.xml and tests in IoC.Configuration.Tests.AutoServiceCustom.AutoServiceCustomSuccessfulLoadTests.cs for more examples on autoServiceCustom element.
Note
This wiki uses simpler examples in IoC.Configuration.Tests.DocumentationTests.AutoServiceCustom.DemoAutoServiceCustom.cs.
Below is a simple example of setting up custom auto-service in configuration file (a segment copied from configuration file DemoIoCConfiguration_autoServiceCustom.xml.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | <dependencyInjection>
<modules>
</modules>
<services>
</services>
<autoGeneratedServices>
<!--Interface specified in autoServiceCustom is auto-implemented by implementation of
IoC.Configuration.ConfigurationFile.ICustomAutoServiceCodeGenerator IoC.Configuration.Tests.AutoServiceCustom.SimpleDataRepository.RepositoryInterfaceImplementationGenerator
that is specified in autoServiceCodeGenerator element.-->
<autoServiceCustom interface="IoC.Configuration.Tests.DocumentationTests.AutoServiceCustom.ISimpleAutoImplementedInterface1">
<autoServiceCodeGenerator>
<constructedValue typeRef="DemoCustomAutoServiceCodeGenerator">
<parameters>
<classMember name="connectionString"
classRef="ConnectionStrings"
memberName="ConnectionString1" />
</parameters>
</constructedValue>
</autoServiceCodeGenerator>
</autoServiceCustom>
<autoServiceCustom interface="IoC.Configuration.Tests.DocumentationTests.AutoServiceCustom.ISimpleAutoImplementedInterface2">
<autoServiceCodeGenerator>
<constructedValue typeRef="DemoCustomAutoServiceCodeGenerator">
<parameters>
<classMember name="connectionString"
classRef="ConnectionStrings"
memberName="ConnectionString1" />
</parameters>
</constructedValue>
</autoServiceCodeGenerator>
</autoServiceCustom>
</autoGeneratedServices>
</dependencyInjection>
|
This configuration instructs IoC.Configuration to generate an implementation of interfaces IoC.Configuration.Tests.DocumentationTests.AutoServiceCustom.ISimpleAutoImplementedInterface1.cs and IoC.Configuration.Tests.DocumentationTests.AutoServiceCustom.ISimpleAutoImplementedInterface2.cs using class IoC.Configuration.Tests.DocumentationTests.AutoServiceCustom.DemoCustomAutoServiceCodeGenerator.cs specified in child element autoServiceCodeGenerator.
Below is the code in IoC.Configuration.Tests.DocumentationTests.AutoServiceCustom.DemoCustomAutoServiceCodeGenerator.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | using System;
using System.IO;
using System.Linq;
using System.Reflection;
using IoC.Configuration.ConfigurationFile;
using IoC.Configuration.DiContainer;
using IoC.Configuration.Tests.AutoServiceCustom.SimpleDataRepository;
using NUnit.Framework;
using OROptimizer;
using OROptimizer.DynamicCode;
using SharedServices.Interfaces;
namespace IoC.Configuration.Tests.DocumentationTests.AutoServiceCustom;
/// <summary>
/// This is a simple demo to demonstrate an implementation of <see cref="ICustomAutoServiceCodeGenerator"/>.
/// For a better example reference <see cref="RepositoryInterfaceImplementationGenerator"/> used in tests
/// in AutoServiceCustom.
/// The best use of <see cref="ICustomAutoServiceCodeGenerator"/> is to generate interface implementation based
/// on attributes applied to interface and interface methods (such as auto-generating entity framework interfaces based on
/// table names, and column metadata attributes).
/// </summary>
public class DemoCustomAutoServiceCodeGenerator : ICustomAutoServiceCodeGenerator
{
public DemoCustomAutoServiceCodeGenerator(string connectionString)
{
// Demo passing parameters to ICustomAutoServiceCodeGenerator in configuration file.
Assert.AreEqual(ConnectionStrings.ConnectionString1, connectionString);
}
/// <inheritdoc />
public void Validate(ICustomAutoGeneratedServiceInfo customAutoGeneratedServiceInfo)
{
var implementedInterfaceType = customAutoGeneratedServiceInfo.ImplementedInterface;
if (!implementedInterfaceType.IsInterface ||
implementedInterfaceType.GetInterfaces().Length > 0 ||
implementedInterfaceType.GetMethods().Length != 1 ||
implementedInterfaceType.GetProperties().Length != 0)
throw new Exception($"The demo auto-implemented interface should not have a parent interfaces and should have exactly one method.");
var methodInfo = implementedInterfaceType.GetMethods().First();
if (methodInfo.GetParameters().Length != 0 || methodInfo.ReturnType != typeof(int))
throw new Exception($"The demo auto-implemented method should be veryyy simple to be short!!.");
if (methodInfo.GetCustomAttributes().FirstOrDefault(x => x is SimpleMethodMetadataAttribute) == null)
throw new Exception($"Method should have an attribute of type '{typeof(SimpleMethodMetadataAttribute)}'.");
}
/// <inheritdoc />
public void GenerateCSharp(ICustomAutoGeneratedServiceInfo customAutoGeneratedServiceInfo, IDynamicAssemblyBuilder dynamicAssemblyBuilder, string generatedClassNamespace, string generatedClassName)
{
// Use IDynamicAssemblyBuilder.AddReferencedAssembly(string assemblyPath) or
// IDynamicAssemblyBuilder.AddReferencedAssembly(Type typeInAssembly) to add assemblies that will be
// referenced by auto-generated assembly if types in these assemblies are used in auto-generated code.
dynamicAssemblyBuilder.AddReferencedAssembly(Path.Combine(Helpers.GetTestFilesFolderPath(), @"DynamicallyLoadedDlls\TestProjects.DynamicallyLoadedAssembly1.dll"));
dynamicAssemblyBuilder.AddReferencedAssembly(typeof(IInterface1));
// By now Validate(ICustomAutoGeneratedServiceInfo customAutoGeneratedServiceInfo) already validated
// that a single method with attribute is present in interface.
var methodInfo = customAutoGeneratedServiceInfo.ImplementedInterface.GetMethods().First();
var attribute = (SimpleMethodMetadataAttribute)methodInfo.GetCustomAttributes().FirstOrDefault(x => x is SimpleMethodMetadataAttribute);
var dynamicClass = dynamicAssemblyBuilder.StartDynamicallyGeneratedClass(generatedClassName,
new[]
{
customAutoGeneratedServiceInfo.ImplementedInterface.GetTypeNameInCSharpClass()
},
generatedClassNamespace);
var methodData = dynamicClass.StartInterfaceImplementationMethod(methodInfo, false);
methodData.AddCodeLine("{");
methodData.AddCodeLine("var testReferencedAssembly = new DynamicallyLoadedAssembly1.Dog(40);");
methodData.AddCodeLine($"return {attribute.ReturnedValue};");
methodData.AddCodeLine("}");
}
/// <inheritdoc />
public void ValidateOnIoCContainerLoaded(IDiContainer diContainer, ICustomAutoGeneratedServiceInfo customAutoGeneratedServiceInfo)
{
// At this point the DI container diContainer is loaded. Do validation using some services in container
// and throw an exception if necessary
//diContainer.Resolve()
}
}
|
Look at test class below for an example of setting up and initializing the DI container from configuration file, and resolving and using auto-generated interfaces IoC.Configuration.Tests.DocumentationTests.AutoServiceCustom.ISimpleAutoImplementedInterface1.cs and IoC.Configuration.Tests.DocumentationTests.AutoServiceCustom.ISimpleAutoImplementedInterface2.cs.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | // Copyright (c) IoC.Configuration Project. All rights reserved.
// Licensed under the MIT License. See LICENSE in the solution root for license information.
using System.IO;
using IoC.Configuration.DiContainerBuilder;
using IoC.Configuration.DiContainerBuilder.FileBased;
using NUnit.Framework;
using OROptimizer.Utilities.Xml;
using TestsSharedLibrary;
namespace IoC.Configuration.Tests.DocumentationTests.AutoServiceCustom;
[TestFixture]
public class DemoAutoServiceCustom
{
private static IContainerInfo _containerInfo;
[SetUp]
public static void TestSetUp()
{
TestsHelper.SetupLogger();
var fileBasedConfigurationParameters = new FileBasedConfigurationParameters(
new FileBasedConfigurationFileContentsProvider(
Path.Combine(Helpers.TestsEntryAssemblyFolder, @"DocumentationTests\AutoServiceCustom\DemoIoCConfiguration_autoServiceCustom.xml")),
Helpers.TestsEntryAssemblyFolder,
// LoadedAssembliesForTests is an implementation of ILoadedAssemblies that has a method
// "IEnumerable<Assembly> GetAssemblies()" that returns list of assemblies to add as references to
// generate dynamic assembly.
new LoadedAssembliesForTests())
{
AdditionalReferencedAssemblies = new []
{
// List additional assemblies that should be added to dynamically generated assembly as references
Path.Combine(Helpers.GetTestFilesFolderPath(), @"DynamicallyLoadedDlls\TestProjects.DynamicallyLoadedAssembly1.dll"),
Path.Combine(Helpers.GetTestFilesFolderPath(), @"DynamicallyLoadedDlls\TestProjects.DynamicallyLoadedAssembly2.dll")
},
AttributeValueTransformers = new[] {new FileFolderPathAttributeValueTransformer()},
ConfigurationFileXmlDocumentLoaded = (sender, e) =>
Helpers.EnsureConfigurationDirectoryExistsOrThrow(e.XmlDocument.SelectElement("/iocConfiguration/appDataDir").GetAttribute("path"))
};
_containerInfo = new DiContainerBuilder.DiContainerBuilder()
.StartFileBasedDi(fileBasedConfigurationParameters, out _)
.WithoutPresetDiContainer()
.RegisterModules().Start();
}
[Test]
public void Demo()
{
var simpleAutoImplementedInterface1 = _containerInfo.DiContainer.Resolve<ISimpleAutoImplementedInterface1>();
Assert.AreEqual(10, simpleAutoImplementedInterface1.GetValue());
var simpleAutoImplementedInterface2 = _containerInfo.DiContainer.Resolve<ISimpleAutoImplementedInterface2>();
Assert.AreEqual(20, simpleAutoImplementedInterface2.GetSomeOtherValue());
}
[TearDown]
public static void TestTeaDown()
{
_containerInfo.Dispose();
}
}
|