Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support nested redirects with call constraints #70

Merged
merged 1 commit into from
May 3, 2023
Merged
Show file tree
Hide file tree
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
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