Skip to content

Commit

Permalink
Support nested redirects with call constraints (#70)
Browse files Browse the repository at this point in the history
  • Loading branch information
devodo authored May 3, 2023
1 parent 4f9567a commit 0f88100
Show file tree
Hide file tree
Showing 7 changed files with 190 additions and 41 deletions.
9 changes: 5 additions & 4 deletions docs/documentation/dependency_injection.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,16 @@ After reset all resolved redirect proxies will be returned to their initial stat
*Nested registrations* are used to redirect calls on types that are not directly resolved by the DI container such as instances created by factory services.

E.g. if we have an `IBarFactory` factory service, resolved by the DI, that has factory methods that create `IBar` instances.
A nested `IBar` registration off a parent `IBarFactory` registration can be declared as follows:
A nested `IBar` redirect registration from the parent `IBarFactory` registration can be declared as follows:

```csharp
var diverter = new Diverter()
var diverter = new DiverterBuilder()
.Register<IBarFactory>(x => x
.ThenRegister<IBar>());
.ThenRedirect<IBar>()) // Proxy redirect any IBar instances returned by IBarFactory
.Create();
```

> Nested registrations on nested registration is supported.
> Nested registrations of nested registrations can be chained to any depth.
{: .note }

This is installed into the existing `IServiceCollection` as usual:
Expand Down
6 changes: 3 additions & 3 deletions src/DivertR/DiverterBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@ public DiverterBuilder(IRedirectSet redirectSet)
public IRedirectSet RedirectSet { get; }

/// <inheritdoc />
public IDiverterBuilder Register<TTarget>(Action<INestedRegisterBuilder>? nestedRegisterAction = null) where TTarget : class?
public IDiverterBuilder Register<TTarget>(Action<INestedRegisterBuilder<TTarget>>? nestedRegisterAction = null) where TTarget : class?
{
return Register<TTarget>(null, nestedRegisterAction);
return Register(null, nestedRegisterAction);
}

/// <inheritdoc />
public IDiverterBuilder Register<TTarget>(string? name, Action<INestedRegisterBuilder>? nestedRegisterAction = null) where TTarget : class?
public IDiverterBuilder Register<TTarget>(string? name, Action<INestedRegisterBuilder<TTarget>>? nestedRegisterAction = null) where TTarget : class?
{
var redirect = RedirectSet.GetOrCreate<TTarget>(name);

Expand Down
4 changes: 2 additions & 2 deletions src/DivertR/IDiverterBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public interface IDiverterBuilder
/// <typeparam name="TTarget">The type to register.</typeparam>
/// <returns>This <see cref="IDiverterBuilder"/> instance.</returns>
/// <exception cref="DiverterException">Thrown if the <typeparamref name="TTarget"/> type with with default <see cref="RedirectId.Name" /> has already been registered.</exception>
IDiverterBuilder Register<TTarget>(Action<INestedRegisterBuilder>? nestedRegisterAction = null) where TTarget : class?;
IDiverterBuilder Register<TTarget>(Action<INestedRegisterBuilder<TTarget>>? nestedRegisterAction = null) where TTarget : class?;

/// <summary>
/// Register a type to redirect.
Expand All @@ -33,7 +33,7 @@ public interface IDiverterBuilder
/// <typeparam name="TTarget">The type to register.</typeparam>
/// <returns>This <see cref="IDiverterBuilder"/> instance.</returns>
/// <exception cref="DiverterException">Thrown if the <typeparamref name="TTarget"/> type with matching <paramref name="name"/> has already been registered.</exception>
IDiverterBuilder Register<TTarget>(string? name, Action<INestedRegisterBuilder>? nestedRegisterAction = null) where TTarget : class?;
IDiverterBuilder Register<TTarget>(string? name, Action<INestedRegisterBuilder<TTarget>>? nestedRegisterAction = null) where TTarget : class?;

/// <summary>
/// Register a type to redirect.
Expand Down
49 changes: 34 additions & 15 deletions src/DivertR/INestedRegisterBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,44 +1,63 @@
using System;
using System.Linq.Expressions;

namespace DivertR
{
/// <summary>
/// A builder interface for providing nested register actions on parent registrations.
/// Nested registrations can be used to intercept and redirect inner services created outside the dependency injection container e.g. from factories.
/// </summary>
public interface INestedRegisterBuilder
public interface INestedRegisterBuilder<TTarget> where TTarget : class?
{
/// <summary>
/// Add a nested registration to redirect calls from the parent registration with return type <typeparamref name="TReturn"/>
/// by proxying instances returned from parent methods via an <see cref="IRedirect{TReturn}"/>.
/// A corresponding <see cref="IRedirect{TReturn}"/> is created and added to the underlying <see cref="IRedirectSet"/>.
/// The returned instances are proxied by inserting a <see cref="IVia"/> on the <see cref="IRedirect{TReturn}"/> that is persistent, i.e. remains after reset.
/// Redirect calls from the parent registration with return type <typeparamref name="TReturn"/> by proxying instances returned from parent methods via an <see cref="IRedirect{TReturn}"/>.
/// The returned instances are proxied by inserting a persistent <see cref="IVia"/> on the <see cref="IRedirect{TReturn}"/>.
/// </summary>
/// <param name="registerAction">Optional nested register action.</param>
/// <typeparam name="TReturn">The return type of calls to redirect.</typeparam>
/// <returns>This <see cref="INestedRegisterBuilder"/> instance.</returns>
/// <returns>This <see cref="INestedRegisterBuilder{TTarget}"/> instance.</returns>
/// <exception cref="DiverterException">Thrown if a nested <see cref="IRedirect{TReturn}"/> has already been registered on the parent with matching <typeparamref name="TReturn"/> type and default <see cref="RedirectId.Name" />.</exception>
INestedRegisterBuilder ThenRegister<TReturn>(Action<INestedRegisterBuilder>? registerAction = null) where TReturn : class?;
INestedRegisterBuilder<TTarget> ThenRedirect<TReturn>(Action<INestedRegisterBuilder<TReturn>>? registerAction = null) where TReturn : class?;

/// <summary>
/// Add a nested registration to redirect calls from the parent registration with return type <typeparamref name="TReturn"/>
/// by proxying instances returned from parent methods via an <see cref="IRedirect{TReturn}"/>.
/// A corresponding <see cref="IRedirect{TReturn}"/> is created and added to the underlying <see cref="IRedirectSet"/>.
/// The returned instances are proxied by inserting a <see cref="IVia"/> on the <see cref="IRedirect{TReturn}"/> that is persistent, i.e. remains after reset.
/// Redirect calls from the parent registration with return type <typeparamref name="TReturn"/> by proxying instances returned from parent methods via an <see cref="IRedirect{TReturn}"/>.
/// The returned instances are proxied by inserting a persistent <see cref="IVia"/> on the <see cref="IRedirect{TReturn}"/>.
/// </summary>
/// <param name="name">Specify the <see cref="DivertR.RedirectId.Name" /> of the returned <see cref="IRedirect{TReturn}"/>.</param>
/// <param name="registerAction">Optional nested register action.</param>
/// <typeparam name="TReturn">The return type of calls to redirect.</typeparam>
/// <returns>This <see cref="INestedRegisterBuilder"/> instance.</returns>
/// <returns>This <see cref="INestedRegisterBuilder{TTarget}"/> instance.</returns>
/// <exception cref="DiverterException">Thrown if a nested <see cref="IRedirect{TReturn}"/> has already been registered on the parent with matching <typeparamref name="TReturn"/> type and <paramref name="name"/>.</exception>
INestedRegisterBuilder ThenRegister<TReturn>(string? name, Action<INestedRegisterBuilder>? registerAction = null) where TReturn : class?;
INestedRegisterBuilder<TTarget> ThenRedirect<TReturn>(string? name, Action<INestedRegisterBuilder<TReturn>>? registerAction = null) where TReturn : class?;

/// <summary>
/// Redirect calls matching a constraint from the parent registration by proxying instances returned from parent methods via an <see cref="IRedirect{TReturn}"/>.
/// The returned instances are proxied by inserting a persistent <see cref="IVia"/> on the <see cref="IRedirect{TReturn}"/>.
/// </summary>
/// <param name="constraintExpression">The call constraint expression.</param>
/// <param name="registerAction">Optional nested register action.</param>
/// <typeparam name="TReturn">The return type of calls to redirect.</typeparam>
/// <returns>This <see cref="INestedRegisterBuilder{TTarget}"/> instance.</returns>
INestedRegisterBuilder<TTarget> ThenRedirect<TReturn>(Expression<Func<TTarget, TReturn>> constraintExpression, Action<INestedRegisterBuilder<TReturn>>? registerAction = null) where TReturn : class?;

/// <summary>
/// Redirect calls matching a constraint from the parent registration by proxying instances returned from parent methods via an <see cref="IRedirect{TReturn}"/>.
/// The returned instances are proxied by inserting a persistent <see cref="IVia"/> on the <see cref="IRedirect{TReturn}"/>.
/// </summary>
/// <param name="name">Specify the <see cref="DivertR.RedirectId.Name" /> of the returned <see cref="IRedirect{TReturn}"/>.</param>
/// <param name="constraintExpression">The call constraint expression.</param>
/// <param name="registerAction">Optional nested register action.</param>
/// <typeparam name="TReturn">The return type of calls to redirect.</typeparam>
/// <returns>This <see cref="INestedRegisterBuilder{TTarget}"/> instance.</returns>
INestedRegisterBuilder<TTarget> ThenRedirect<TReturn>(string? name, Expression<Func<TTarget, TReturn>> constraintExpression, Action<INestedRegisterBuilder<TReturn>>? registerAction = null) where TReturn : class?;

/// <summary>
/// Register a decorator on call returns of type <typeparamref name="TReturn"/> on the nested redirect.
/// The decorator is applied by inserting a <see cref="IVia"/> on the nested redirect that is persistent, i.e. remains after reset.
/// </summary>
/// <param name="decorator">The decorator function.</param>
/// <typeparam name="TReturn">The return type of calls to decorate.</typeparam>
/// <returns>This <see cref="INestedRegisterBuilder"/> instance.</returns>
INestedRegisterBuilder ThenDecorate<TReturn>(Func<TReturn, TReturn> decorator);
/// <returns>This <see cref="INestedRegisterBuilder{TTarget}"/> instance.</returns>
INestedRegisterBuilder<TTarget> ThenDecorate<TReturn>(Func<TReturn, TReturn> decorator);
}
}
31 changes: 26 additions & 5 deletions src/DivertR/Internal/NestedRegisterBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using System;
using System.Collections.Concurrent;
using System.Linq.Expressions;

namespace DivertR.Internal
{
internal class NestedRegisterBuilder<TTarget> : INestedRegisterBuilder where TTarget : class?
internal class NestedRegisterBuilder<TTarget> : INestedRegisterBuilder<TTarget> where TTarget : class?
{
private readonly IRedirect<TTarget> _redirect;
private readonly ConcurrentDictionary<RedirectId, ConcurrentDictionary<RedirectId, IRedirect>> _registeredRedirects;
Expand All @@ -14,12 +15,12 @@ public NestedRegisterBuilder(IRedirect<TTarget> redirect, ConcurrentDictionary<R
_registeredRedirects = registeredRedirects;
}

public INestedRegisterBuilder ThenRegister<TReturn>(Action<INestedRegisterBuilder>? registerAction = null) where TReturn : class?
public INestedRegisterBuilder<TTarget> ThenRedirect<TReturn>(Action<INestedRegisterBuilder<TReturn>>? registerAction = null) where TReturn : class?
{
return ThenRegister<TReturn>(null, registerAction);
return ThenRedirect((string?) null, registerAction);
}

public INestedRegisterBuilder ThenRegister<TReturn>(string? name, Action<INestedRegisterBuilder>? registerAction = null) where TReturn : class?
public INestedRegisterBuilder<TTarget> ThenRedirect<TReturn>(string? name, Action<INestedRegisterBuilder<TReturn>>? registerAction = null) where TReturn : class?
{
var nestedRedirect = _redirect.RedirectSet.GetOrCreate<TReturn>(name);

Expand All @@ -39,7 +40,27 @@ public INestedRegisterBuilder ThenRegister<TReturn>(string? name, Action<INested
return this;
}

public INestedRegisterBuilder ThenDecorate<TReturn>(Func<TReturn, TReturn> decorator)
public INestedRegisterBuilder<TTarget> ThenRedirect<TReturn>(Expression<Func<TTarget, TReturn>> constraintExpression, Action<INestedRegisterBuilder<TReturn>>? registerAction = null) where TReturn : class?
{
return ThenRedirect(null, constraintExpression, registerAction);
}

public INestedRegisterBuilder<TTarget> ThenRedirect<TReturn>(string? name, Expression<Func<TTarget, TReturn>> constraintExpression, Action<INestedRegisterBuilder<TReturn>>? registerAction = null) where TReturn : class?
{
var nestedRedirect = _redirect
.To(constraintExpression)
.ViaRedirect(name, opt =>
{
opt.DisableSatisfyStrict();
opt.Persist();
});

registerAction?.Invoke(new NestedRegisterBuilder<TReturn>(nestedRedirect, _registeredRedirects));

return this;
}

public INestedRegisterBuilder<TTarget> ThenDecorate<TReturn>(Func<TReturn, TReturn> decorator)
{
_redirect.ViaDecorator(decorator, opt =>
{
Expand Down
Loading

0 comments on commit 0f88100

Please sign in to comment.