From 2dc8f40f119260697f004fcaf96e3053f639602b Mon Sep 17 00:00:00 2001 From: Byron Mayne Date: Sat, 20 Dec 2025 11:58:28 -0500 Subject: [PATCH] Updated the readme of the project --- README.md | 247 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 141 insertions(+), 106 deletions(-) diff --git a/README.md b/README.md index 3263dcc..419e77c 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,21 @@ [![NuGet Downloads](http://img.shields.io/nuget/dt/Ninject.Extension.AutoFactories.svg?style=flat)](https://www.nuget.org/packages/Ninject.Extension.AutoFactories/) ![Issues](https://img.shields.io/github/issues-closed/ByronMayne/Ninject.Extensions.AutoFactories) - ## AutoFactories -This library is a [Source Generator](https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview) that generates factory for types during the compilation process. This removes the need to have to write uninteresting boilerplate code. You can use the standard version `Autofactories` or create factories for one of the supported third party libraries like [Ninject](https://github.com/ninject/Ninject) or [Microsoft.DependencyInjection](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection). Do you want support for another framwork, create a request. +### The Problem + +When using Dependency Injection (DI) containers, you often need to create new instances of objects at runtime—not just at startup. However, doing this directly with the container in your application code defeats the whole purpose of DI: keeping your code decoupled from the container. + +The typical solution is to use **factories**: wrapper classes that handle the complexity of creating instances while keeping the container logic isolated. Unfortunately, writing factories is tedious boilerplate code that gets repeated across your codebase. + +### The Solution + +AutoFactories automatically generates factory classes for you during compilation. Just add a single attribute to your class, and you get a fully-typed factory with all the boilerplate written for you—automatically. ## Quick Example -This code below uses this library to auto generate the `IShippingOrderFactory` and it's `Get` method to create new instances of `ShippingOrder` with both user provider and DI provided parameters. +This code below uses this library to auto generate the `IShippingOrderFactory` and its `Get` method. Just add `[AutoFactory]` to your class, and the factory is generated automatically. Parameters marked with `[FromFactory]` come from your DI container, while others become factory method arguments. ```cs namespace Operations @@ -27,170 +34,178 @@ namespace Operations public static void Main(string[] args) { IShippingProvider provider = new ShippingProvider(); - // The factory is generated, the constructor will take all - // parameters that are marked with 'FromFactory' + // The factory is auto-generated with a constructor for DI dependencies IShippingOrderFactory factory = new ShippingOrderFactory(provider); - // The `Get` method will take all parameters not marked with 'FromFactory' - ShippingOrder shippingOrder = factory.Get(orderId:"A2123F") + // The Create method only takes user-provided parameters + ShippingOrder shippingOrder = factory.Create(orderId:"A2123F") } } } ``` -You can also use a third party library instead of the generic factory. +### Using with Dependency Injection Frameworks + +AutoFactories works seamlessly with popular DI containers: -### Ninject +#### Ninject ```cs public static void Main(string[] args) { IKernel kernel = new StandardKernel() - .AddAutoFactories(); // Adds the required view - IShippingOrderFactory factory = kernel.Get(); - ShippingOrder shippingOrder = factory.Get(orderId:"A2123F") + .AddAutoFactories(); // Registers all generated factories + IShippingOrderFactory factory = kernel.Get(); + ShippingOrder shippingOrder = factory.Create(orderId:"A2123F") } ``` -### Microsoft.DependencyInjection +#### Microsoft.DependencyInjection ```cs using Microsoft.Extensions.DependencyInjection; public static void Main(string[] args) { IServiceProvider serviceProvider = new ServiceCollection() - .AddAutoFactories() // Adds the generated factories + .AddAutoFactories() // Registers all generated factories .BuildServiceProvider(); - IShippingOrderFactory factory = serviceProvider.GetRequiredService(); - ShippingOrder shippingOrder = factory.Get(orderId:"A2123F") + IShippingOrderFactory factory = serviceProvider.GetRequiredService(); + ShippingOrder shippingOrder = factory.Create(orderId:"A2123F") } ``` -## Introduction -The best practice for using a Dependency Injection (DI) container in a software system is to restrict direct interaction with the container to the Composition Root. +## How It Works + +### What Gets Generated? + +When you apply `[AutoFactory]` to a class, AutoFactories generates: -However, many applications face the challenge of not being able to instantiate all dependencies at startup or at the beginning of each request, due to incomplete information at those times. These applications require a method to create new instances later using the Kernel. This should be done without referencing the container's types outside of the Composition Root. This is where factories come in. +1. **A Factory Class** - contains the factory methods +2. **A Factory Interface** - for dependency injection +3. **Create Methods** - one for each constructor in your class +Parameters marked with `[FromFactory]` are automatically resolved by the container. All other parameters become method arguments, with compile-time type safety (no more magic string parameter names). -Lets say we have this the following class: +### Example + +This class: ```csharp +[AutoFactory] public class Coffee { - private IMilkService m_milkService; + public Size Size { get; } + public IMilkService Milk { get; } - public Coffee(IMilkService milkService) - { - m_milkService = milkService; - } + public Coffee( + Size size, + [FromFactory] IMilkService milkService) + {} } ``` -If we wanted to have the ability to create new `Coffee` we would create a factory: -```csharp -public class CoffeeFactory +Generates this factory (simplified): + +```csharp +public interface ICoffeeFactory +{ + Coffee Create(Size size); +} + +internal partial class CoffeeFactory : ICoffeeFactory { - private IKernel m_kernel; + private readonly IResolutionRoot m_resolutionRoot; - public CoffeeFactory(IKernel kernel) + public CoffeeFactory(IResolutionRoot resolutionRoot) { - m_kernel = kernel; + m_resolutionRoot = resolutionRoot; } - public Foo Create() - => m_kernel.Get(): + public Coffee Create(Size size) + { + return m_resolutionRoot.Get( + new ConstructorParameter(nameof(size), size)); + } } ``` -Having this factory means that the `IMilkService` would be resolved by the `Kernel` and we don't have to worry about passing it in. +Notice: the parameter name is derived from your source code, so if someone refactors `size` to `coffeeSize`, you get a compile-time error—not a runtime crash. -If we wanted to add a user parameter like `Size` we could update the factory to reflect this. +## Why Use Factories? -```csharp -using Ninject.Parameters; +### The Traditional Problem -public class Coffee -{ - public Size Size { get; } - private IMilkService m_milkService; +Without factories, you have two bad choices: - public Coffee(IMilkService milkService, Size size) - { - Size = size; - m_milkService = milkService; - } -} +1. **Reference the container everywhere** - Your business logic becomes tightly coupled to the DI framework +2. **Manually write factories** - Lots of repetitive boilerplate that's easy to get wrong +AutoFactories solves both problems by generating type-safe factories automatically. -public class CoffeeFactory -{ - private IKernel m_kernel; +## Attribute Options - public CoffeeFactory(IKernel kernel) - { - m_kernel = kernel; - } +The `[AutoFactory]` attribute is highly configurable, giving you control over how factories are generated: - public Foo Create(Size size) - { - return m_kernel.Get(new IParameter[] { - new ConstructorParameter("size", size) - }): - } -} +### Default Behavior +```csharp +[AutoFactory] +public class Coffee { } ``` -There is a problem here and is the hard coded parameter `size`. With Ninject to provide external parameters you need the exact parameter, `size`. If someone were to go in in change `size` to `coffeeSize` this call would throw a runtime exception. This is what this library tries to do by making this a compile time error instead. - -## How It Works - -To generate factories for your types you apply the `[AutoFactory]` attribute. A new factory will be generated for ever class that has this attribute. For every single constructor a `Create{ClassName}` method will be generated as well. Any parameters that don't have `[FromFactory]` attribute applied will be arguments of the generated `Create` methods. For example +Generates a factory class named `CoffeeFactory` with an interface `ICoffeeFactory` and a method named `Create()`. +### Custom Method Name +Specify the name of the factory method: ```csharp [AutoFactory] -public class Coffee -{ - public Size Size { get; } - public IMilkService Milk { get; } +public class Coffee { } - public Coffee( - Size size, - [FromFactory] IMilkService milkService) - {} -} +[AutoFactory] +public class Espresso { } + +public partial class BeverageFactory { } + +[AutoFactory(typeof(BeverageFactory), "MakeCoffee")] +public class Coffee { } + +[AutoFactory(typeof(BeverageFactory), "MakeEspresso")] +public class Espresso { } ``` -Would generate: +This generates both `MakeCoffee()` and `MakeEspresso()` methods in a single `BeverageFactory` class, eliminating naming collisions. + +### Shared Factory for Multiple Types +Combine multiple types into one factory: ```csharp -internal partial class CoffeeFactory : ICoffeeFactory -{ - private readonly IResolutionRoot m_resolutionRoot; +public partial class AnimalFactory { } - public CoffeeFactory(IResolutionRoot resolutionRoot) - { - m_resolutionRoot = resolutionRoot; - } +[AutoFactory(typeof(AnimalFactory), "CreateCat")] +public class Cat +{ + public Cat(string name, [FromFactory] IVeterinarian vet) { } +} - public CreateCoffee(Size size) - { - IParamater[] parameters = new IParameter[] - { - new ConstructorParameter(nameof(size), size) - }; - return m_resolutionRoot.Get(); - } +[AutoFactory(typeof(AnimalFactory), "CreateDog")] +public class Dog +{ + public Dog(string breed, [FromFactory] IVeterinarian vet) { } } ``` - -All the factories are registered in the generate `Ninject.FactoriesModule` which you need to register to your `IKernel`. To do this use the extension method. - +Generates: ```csharp -IKernel kernel = new StandardKernel(); -kernel.LoadFactories(); -ICoffeeFactory coffeeFactory = kernel.Get(); +public partial class AnimalFactory : IAnimalFactory +{ + private readonly IVeterinarian m_vet; + + public AnimalFactory(IVeterinarian vet) => m_vet = vet; + + public Cat CreateCat(string name) => new Cat(name, m_vet); + public Dog CreateDog(string breed) => new Dog(breed, m_vet); +} ``` -## Properties +## Advanced Usage ### Expose As -Apply to a class to change the return type of the generated factory method. +Change the return type of the generated factory method using `ExposeAs`: + ```cs public interface IClock { @@ -204,17 +219,37 @@ public class Clock : IClock public Clock(int ticks) { - Ticks = tick; + Ticks = ticks; } } ``` -Would produce +Generates: + ```cs -public class ClockFactory : IClockFactory +public interface IClockFactory { - // Returns `IClock` instead of `Clock` - public IClock Create(int ticks) - => new Clock(ticks); + // Returns the interface, not the concrete type + IClock Create(int ticks); } -``` \ No newline at end of file +``` + +## Technical Details + +### What is AutoFactories? + +AutoFactories is a [Source Generator](https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview) that generates factory code during compilation. The generated code is: + +- **Type-safe** - Full IntelliSense and compile-time checking +- **Zero-overhead** - No reflection or runtime costs +- **Standard C#** - Works with any C# 9+ project + +### Framework Support + +AutoFactories provides special integrations for: + +- **Generic factories** - No framework dependencies +- **Ninject** - Full integration with Ninject's kernel +- **Microsoft.DependencyInjection** - Works with ASP.NET Core and other frameworks + +Don't see your framework? [Open a feature request](https://github.com/ByronMayne/AutoFactories/issues/new). \ No newline at end of file