Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
247 changes: 141 additions & 106 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<ShippingOrderFactory>();
ShippingOrder shippingOrder = factory.Get(orderId:"A2123F")
.AddAutoFactories(); // Registers all generated factories
IShippingOrderFactory factory = kernel.Get<IShippingOrderFactory>();
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<ShippingOrderFactory>();
ShippingOrder shippingOrder = factory.Get(orderId:"A2123F")
IShippingOrderFactory factory = serviceProvider.GetRequiredService<IShippingOrderFactory>();
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<Foo>():
public Coffee Create(Size size)
{
return m_resolutionRoot.Get<Coffee>(
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<Foo>(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<Coffee>();
}
[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<ICoffeeFactory>();
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
{
Expand All @@ -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);
}
```
```

## 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).
Loading