Back on Github with #dynamic-fluent-apis

Back on Github with #dynamic-fluent-apis

Check out this project on my github.com page. If you need more info about fluent API, here's a long read on Wikipedia.

DynamicFluentApis is a .NET (Framework 4.6.1) project I've recently written in C#. It provides functionalities that allow you to build dynamic .NET assemblies with fluent API support, for types located in other assemblies. Simply put, never, ever write code like that again:

using System;


namespace Demo1.LegacyWayOfProvidingFluentApiSupport
{
    public class Program
    {
        public static void Main()
        {
            var p = new PersonWrapper()
                .FirstName("Abdoul")
                .LastName("Kaba")
                .BirthDate(new DateTime(1990,7,29))
                .Object;
            Console.Write($"Hello, {p.FirstName}! You say you are {p.Age} ");
            Console.WriteLine("and your last name is {p.LastName}, right?");
        }


        public interface IPerson
        {
            string FirstName { get; set; }
            string LastName { get; set; }
            DateTime BirthDate { get; set; }
            double Age { get; }
        }


        public class Person : IPerson
        {
            public virtual string FirstName { get; set; }
            public virtual string LastName { get; set; }
            public virtual DateTime BirthDate { get; set; }
            public double Age { get => DateTime.Today.Subtract(BirthDate).TotalDays / 365; }
        }


        public class PersonWrapper
        {
            IPerson _person;


            public PersonWrapper()
            {
                _person = new Person();
            }


            public PersonWrapper(IPerson person)
            {
                _person = person;
            }


            public string FirstName() => _person.FirstName;


            public PersonWrapper FirstName(string value)
            {
                _person.FirstName = value;
                return this;
            }


            public string LastName() => _person.LastName;


            public PersonWrapper LastName(string value)
            {
                _person.LastName = value;
                return this;
            }


            public DateTime BirthDate() => _person.BirthDate;


            public PersonWrapper BirthDate(DateTime value)
            {
                _person.BirthDate = value;
                return this;
            }


            public IPerson Object { get => _person; }
        }
    }
}        

What's the problem with the code above? Technically, not much! But, for every single type you want to be fluent API capable, you need to manually write a wrapper class like `PersonWrapper` to be able to chain method calls. It can be tedious when there's an assembly with dozens or even hundreds of remarkable types you want to support.

That's where DynamicFluentApis steps in and generates all that boiler-plate code for you. So, instead of writing code like the one above, you just let DynamicFluentApis generate another dynamic assembly that supports fluent API on whatever types you want. You then grab that assembly and add it as a reference to your latest hot project.

Steps to generate a dynamic assembly with fluent API support

First things first:

  1. From your favourite command line or terminal, change the working directory to one of your liking and then pull this repo with git (or download a zipped version): `> git clone https://github.com/bigabdoul/dynamic-fluent-apis.git`
  2. Reference both projects, namely `DynamicFluentApis` and `DynamicFluentApis.Core`, within a new console or a unit test project (using .NET Framework 4.6.1).
  3. Add a reference to the console project from the library/libraries or project(s) to which you want to provide fluent API support. Suppose we call these referenced assemblies `HumanResources` and `HumanResources.Web.Mvc`.
  4. Pretending that `HumanResources` contains the `Person` class and `HumanResources.Web.Mvc` the `BootstrapModalModel` class, write the following code to get started:

using System;
using static System.Console;
using DynamicFluentApis;
using DynamicFluentApis.Core;
using HumanResources;
using HumanResources.Web.Mvc;


namespace Demo2.GeneratingDynamicAssemblies
{
    public class Program
    {
        public static void Main()
        {
            try
            {
                // generate a dynamic assembly for the single Person class // (very unlikely); minimalistic approach:var result = FluentApiFactory
                    .Configure()
                    .Scan(typeof(Person))
                    .Build()
                    .ReleaseResources()
                    .Result();

                WriteAssemblyLocation(result);


                // or if you want to scan the whole HumanResources assembly// you have to be explicit; the ScanAssembly(Assembly) and// ScanAssemblyFrom(Type) methods retrieve only types marked// with the custom attribute FluentApiTargetAttributevar types = typeof(Person).Assembly.GetTypes();

                result = FluentApiFactory
                                .Configure(overwrite: true)
                                .Scan(types)
                                .Build()
                                .ReleaseResources()
                                .Result();

                WriteAssemblyLocation(result);

                // you can even generate multiple dynamic assemblies// with this full-blown approach:using (var config = FluentApiFactory.Configure(true))
                {
                    FluentApiFactoryExecutionResult result1 = null;
                    result = config
                        .OnError(error => 
                                WriteLine($"A critical error occured: {error}")
                        )
                        .OnDeleteError((error, builder, file) =>
                            WriteLine($"Could not delete the file '{file}'. Reason for failure: {error.Message}")
                        )
                        .WithOverwriteOptions()
                        // these 'Set...' methods modify the default prefix and suffix values// public interface IPerson {...}// internal sealed class PersonCloned : IPerson {...}
                        .SetProxyClassNameSuffix("Cloned")
                        .SetFluentTypeNamePrefix("Magic")
                        // public class MagicPerson { ... public virtual IPerson Target { get; } }
                        .SetWrappedObjectPropertyName("Target")
                        .WithConfig()
                        .ScanAssemblyFrom(typeof(Person))
                        .Build()
                        .SetResult(r => result1 = r)
                        .Reset()
                        // default options use 'Proxy' suffix (the above would have been// 'PersonProxy' instead of 'PersonCloned'),// and 'Fluent' prefix ('FluentPerson' instead of 'MagicPerson')
                        .WithDefaultOptions(overwrite: true)
                        .ScanAssemblyFrom(typeof(BootstrapModalModel))
                        // will generate, amongst others, the following types:// public interface IBootstrapModalModel {...}// internal sealed class BootstrapModalModelProxy : IBootstrapModalModel {...}// public class FluentBootstrapModalModel {...}
                        .Build()
                        .Result();


                    WriteAssemblyLocation(result);
                    WriteAssemblyLocation(result1);
                }


                if (result.Succeeded)
                {
                    WriteLine("What's next? Grab that file and a reference to it in your project.");
                    WriteLine("You'll be able to use your fluent wrapper as shown in the next demo.");
                    Write("The assemblies' names are similar to: HumanResources.DynamicFluentApis.abcdef.dll ");
                    WriteLine("and HumanResources.Web.Mvc.DynamicFluentApis.fedcba.dll");
                    Write("Where 'abcdef' and 'fedcba' are (for instance) the hash codes generated ");
                    WriteLine("for the dynamic fluent API assemblies.");
                }
            }
            catch(Exception ex)
            {
                WriteLine($"An unexpected error occured: {ex.Message}");
            }


            void WriteAssemblyLocation(FluentApiFactoryExecutionResult result)
            {
                if (true == result?.Succeeded)
                {
                    WriteLine($"The generated assembly is {Environment.CurrentDirectory}\\{result.AssemblyFileName}!");
                }
                else
                {
                    WriteLine("Could not create the assembly!");
                }
            }
        }
    }
}        

We'll see more about how to use these libraries through a more elaborated demo project very soon. Stay tuned!

To view or add a comment, sign in

Explore content categories