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// Copyright (c) IoC.Configuration Project. All rights reserved.
 2// Licensed under the MIT License. See LICENSE in the solution root for license information.
 3
 4using JetBrains.Annotations;
 5using OROptimizer.DynamicCode;
 6using OROptimizer.ServiceResolver;
 7using System;
 8
 9namespace IoC.Configuration.ConfigurationFile
10{
11    /// <summary>
12    /// Generates an implementation class for interfaces specified in autoServiceCustom element in configuration file.
13    /// </summary>
14    public interface ICustomAutoServiceCodeGenerator
15    {
16        /// <summary>
17        /// Validates the configuration of "autoServiceCustom" element.
18        /// Throws an exception if the configuration is invalid.
19        /// </summary>
20        /// <param name="customAutoGeneratedServiceInfo"></param>
21        void Validate([NotNull] ICustomAutoGeneratedServiceInfo customAutoGeneratedServiceInfo);
22
23        /// <summary>
24        /// Generates a C# code for the auto-implemented interface specified in "autoServiceCustom" element.
25        /// </summary>
26        /// <param name="customAutoGeneratedServiceInfo">Configuration of auto-implemented service configuration.</param>
27        /// <param name="dynamicAssemblyBuilder">
28        /// An instance of <see cref="IDynamicAssemblyBuilder"/>.
29        /// Use methods <see cref="IDynamicAssemblyBuilder.StartDynamicallyGeneratedClass(string, string)"/>,
30        /// <see cref="IDynamicAssemblyBuilder.StartDynamicallyGeneratedClass(string, System.Collections.Generic.IEnumerable{string}, string)"/>,
31        /// <see cref="IDynamicAssemblyBuilder.AddCSharpFile(string)"/>, etc., to generate the C# code for the implementation.
32        /// </param>
33        /// <param name="generatedClassNamespace">The generated class namespace.</param>
34        /// <param name="generatedClassName">The generated class name without namespace.</param>
35        void GenerateCSharp([NotNull] ICustomAutoGeneratedServiceInfo customAutoGeneratedServiceInfo,
36            [NotNull] IDynamicAssemblyBuilder dynamicAssemblyBuilder,
37            [NotNull] string generatedClassNamespace, [NotNull] string generatedClassName);
38
39        /// <summary>
40        /// Validates the configuration of "autoServiceCustom" element after the IoC container is loaded.
41        /// Throws an exception if the configuration is invalid.
42        /// </summary>
43        /// <param name="diContainer">The IoC container. Use IDiContainer.<see cref="IServiceResolver.Resolve(Type)"/> to resolve types.</param>
44        /// <param name="customAutoGeneratedServiceInfo">Information about auto-implemented interface.</param>
45        void ValidateOnIoCContainerLoaded([NotNull] DiContainer.IDiContainer diContainer, [NotNull] ICustomAutoGeneratedServiceInfo customAutoGeneratedServiceInfo);
46    }
47}

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.

Below is a simple example of setting up custom auto-service in configuration file (a segment copied from configuration file DemoIoCConfiguration_autoServiceCustom.xml.

 1<dependencyInjection>
 2    <modules>
 3    </modules>
 4
 5    <services>
 6    </services>
 7
 8    <autoGeneratedServices>
 9
10       <!--Interface specified in autoServiceCustom is auto-implemented by implementation of
11       IoC.Configuration.ConfigurationFile.ICustomAutoServiceCodeGenerator IoC.Configuration.Tests.AutoServiceCustom.SimpleDataRepository.RepositoryInterfaceImplementationGenerator
12       that is specified in autoServiceCodeGenerator element.-->
13
14        <autoServiceCustom interface="IoC.Configuration.Tests.DocumentationTests.AutoServiceCustom.ISimpleAutoImplementedInterface1">
15            <autoServiceCodeGenerator>
16                    <constructedValue typeRef="DemoCustomAutoServiceCodeGenerator">
17                            <parameters>
18                                    <classMember name="connectionString"
19                                        classRef="ConnectionStrings"
20                                        memberName="ConnectionString1" />
21                            </parameters>
22                    </constructedValue>
23            </autoServiceCodeGenerator>
24        </autoServiceCustom>
25
26            <autoServiceCustom interface="IoC.Configuration.Tests.DocumentationTests.AutoServiceCustom.ISimpleAutoImplementedInterface2">
27                    <autoServiceCodeGenerator>
28                            <constructedValue typeRef="DemoCustomAutoServiceCodeGenerator">
29                                    <parameters>
30                                            <classMember name="connectionString"
31                                                classRef="ConnectionStrings"
32                                                memberName="ConnectionString1" />
33                                    </parameters>
34                            </constructedValue>
35                    </autoServiceCodeGenerator>
36            </autoServiceCustom>
37    </autoGeneratedServices>
38</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.

 1using System;
 2using System.IO;
 3using System.Linq;
 4using System.Reflection;
 5using IoC.Configuration.ConfigurationFile;
 6using IoC.Configuration.DiContainer;
 7using IoC.Configuration.Tests.AutoServiceCustom.SimpleDataRepository;
 8using NUnit.Framework;
 9using OROptimizer;
10using OROptimizer.DynamicCode;
11using SharedServices.Interfaces;
12
13namespace IoC.Configuration.Tests.DocumentationTests.AutoServiceCustom;
14
15/// <summary>
16/// This is a simple demo to demonstrate an implementation of <see cref="ICustomAutoServiceCodeGenerator"/>.
17/// For a better example reference <see cref="RepositoryInterfaceImplementationGenerator"/> used in tests
18/// in AutoServiceCustom.
19/// The best use of <see cref="ICustomAutoServiceCodeGenerator"/> is to generate interface implementation based
20/// on attributes applied to interface and interface methods (such as auto-generating entity framework interfaces based on
21/// table names, and column metadata attributes).
22/// </summary>
23public class DemoCustomAutoServiceCodeGenerator : ICustomAutoServiceCodeGenerator
24{
25    public DemoCustomAutoServiceCodeGenerator(string connectionString)
26    {
27        // Demo passing parameters to ICustomAutoServiceCodeGenerator in configuration file.
28        Assert.AreEqual(ConnectionStrings.ConnectionString1, connectionString);
29    }
30
31    /// <inheritdoc />
32    public void Validate(ICustomAutoGeneratedServiceInfo customAutoGeneratedServiceInfo)
33    {
34        var implementedInterfaceType = customAutoGeneratedServiceInfo.ImplementedInterface;
35
36        if (!implementedInterfaceType.IsInterface ||
37            implementedInterfaceType.GetInterfaces().Length > 0 ||
38            implementedInterfaceType.GetMethods().Length != 1 ||
39            implementedInterfaceType.GetProperties().Length != 0)
40            throw new Exception($"The demo auto-implemented interface should not have a parent interfaces and should have exactly one method.");
41
42        var methodInfo = implementedInterfaceType.GetMethods().First();
43
44        if (methodInfo.GetParameters().Length != 0 || methodInfo.ReturnType != typeof(int))
45            throw new Exception($"The demo auto-implemented method should be veryyy simple to be short!!.");
46
47        if (methodInfo.GetCustomAttributes().FirstOrDefault(x => x is SimpleMethodMetadataAttribute) == null)
48            throw new Exception($"Method should have an attribute of type '{typeof(SimpleMethodMetadataAttribute)}'.");
49    }
50
51    /// <inheritdoc />
52    public void GenerateCSharp(ICustomAutoGeneratedServiceInfo customAutoGeneratedServiceInfo, IDynamicAssemblyBuilder dynamicAssemblyBuilder, string generatedClassNamespace, string generatedClassName)
53    {
54        // Use IDynamicAssemblyBuilder.AddReferencedAssembly(string assemblyPath) or
55        // IDynamicAssemblyBuilder.AddReferencedAssembly(Type typeInAssembly) to add assemblies that will be
56        // referenced by auto-generated assembly if types in these assemblies are used in auto-generated code.
57        dynamicAssemblyBuilder.AddReferencedAssembly(Path.Combine(Helpers.GetTestFilesFolderPath(), @"DynamicallyLoadedDlls\TestProjects.DynamicallyLoadedAssembly1.dll"));
58        dynamicAssemblyBuilder.AddReferencedAssembly(typeof(IInterface1));
59
60        // By now Validate(ICustomAutoGeneratedServiceInfo customAutoGeneratedServiceInfo) already validated
61        // that a single method with attribute is present in interface.
62        var methodInfo = customAutoGeneratedServiceInfo.ImplementedInterface.GetMethods().First();
63
64        var attribute = (SimpleMethodMetadataAttribute)methodInfo.GetCustomAttributes().FirstOrDefault(x => x is SimpleMethodMetadataAttribute);
65
66        var dynamicClass = dynamicAssemblyBuilder.StartDynamicallyGeneratedClass(generatedClassName,
67            new[]
68            {
69                customAutoGeneratedServiceInfo.ImplementedInterface.GetTypeNameInCSharpClass()
70            },
71            generatedClassNamespace);
72
73        var methodData = dynamicClass.StartInterfaceImplementationMethod(methodInfo, false);
74
75        methodData.AddCodeLine("{");
76
77        methodData.AddCodeLine("var testReferencedAssembly = new DynamicallyLoadedAssembly1.Dog(40);");
78
79        methodData.AddCodeLine($"return {attribute.ReturnedValue};");
80        methodData.AddCodeLine("}");
81    }
82
83    /// <inheritdoc />
84    public void ValidateOnIoCContainerLoaded(IDiContainer diContainer, ICustomAutoGeneratedServiceInfo customAutoGeneratedServiceInfo)
85    {
86        // At this point the DI container diContainer is loaded. Do validation using some services in container
87        // and throw an exception if necessary
88        //diContainer.Resolve()
89    }
90}

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// Copyright (c) IoC.Configuration Project. All rights reserved.
 2// Licensed under the MIT License. See LICENSE in the solution root for license information.
 3
 4using System.IO;
 5using IoC.Configuration.DiContainerBuilder;
 6using IoC.Configuration.DiContainerBuilder.FileBased;
 7using NUnit.Framework;
 8using OROptimizer.Utilities.Xml;
 9using TestsSharedLibrary;
10
11namespace IoC.Configuration.Tests.DocumentationTests.AutoServiceCustom;
12
13[TestFixture]
14public class DemoAutoServiceCustom
15{
16    private static IContainerInfo _containerInfo;
17
18    [SetUp]
19    public static void TestSetUp()
20    {
21        TestsHelper.SetupLogger();
22
23        var fileBasedConfigurationParameters = new FileBasedConfigurationParameters(
24            new FileBasedConfigurationFileContentsProvider(
25                Path.Combine(Helpers.TestsEntryAssemblyFolder, @"DocumentationTests\AutoServiceCustom\DemoIoCConfiguration_autoServiceCustom.xml")),
26            Helpers.TestsEntryAssemblyFolder,
27            // LoadedAssembliesForTests is an implementation of ILoadedAssemblies that has a method
28            // "IEnumerable<Assembly> GetAssemblies()" that returns list of assemblies to add as references to
29            // generate dynamic assembly.
30            new LoadedAssembliesForTests())
31        {
32            AdditionalReferencedAssemblies = new []
33            {
34                // List additional assemblies that should be added to dynamically generated assembly as references
35                Path.Combine(Helpers.GetTestFilesFolderPath(), @"DynamicallyLoadedDlls\TestProjects.DynamicallyLoadedAssembly1.dll"),
36                Path.Combine(Helpers.GetTestFilesFolderPath(), @"DynamicallyLoadedDlls\TestProjects.DynamicallyLoadedAssembly2.dll")
37            },
38            AttributeValueTransformers = new[] {new FileFolderPathAttributeValueTransformer()},
39            ConfigurationFileXmlDocumentLoaded = (sender, e) =>
40                Helpers.EnsureConfigurationDirectoryExistsOrThrow(e.XmlDocument.SelectElement("/iocConfiguration/appDataDir").GetAttribute("path"))
41        };
42
43        _containerInfo = new DiContainerBuilder.DiContainerBuilder()
44            .StartFileBasedDi(fileBasedConfigurationParameters)
45            .WithoutPresetDiContainer()
46            .RegisterModules().Start();
47    }
48
49    [Test]
50    public void Demo()
51    {
52        var simpleAutoImplementedInterface1 = _containerInfo.DiContainer.Resolve<ISimpleAutoImplementedInterface1>();
53        Assert.AreEqual(10, simpleAutoImplementedInterface1.GetValue());
54
55        var simpleAutoImplementedInterface2 = _containerInfo.DiContainer.Resolve<ISimpleAutoImplementedInterface2>();
56        Assert.AreEqual(20, simpleAutoImplementedInterface2.GetSomeOtherValue());
57    }
58
59    [TearDown]
60    public static void TestTeaDown()
61    {
62        _containerInfo.Dispose();
63    }
64}