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.

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