question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Using external values when resolving

See original GitHub issue

Hi

While trying this out I ran into an issue that I’ve been unable to solve.

Most of our services needs to know the identity of the current user. In our current DI setup we get this from ambient data when resolving (HttpContext.Current to be specific). I’ve never liked that much, but I’m not sure there’s any way around that in old Asp.Net.

I’ve been trying to solve that issue with Pure.DI and have come up with 4(5) solutions, but none are to my satisfaction and one doesn’t compile. So I’m writing now in the hope of some guidance or ideally pointers to some obvious solution that I’ve missed.

I’ll detail the five solutions here:

Two of the solutions are partially implemented in my fork of this project, but neither have been finished. There’s quite a lot of work to do yet and since I’m not really interested in maintaining my own fork, I’m not going to continue the work unless there is some chance of getting the changes merged.

The first api change introduces IConfiguration BindAtResolve<T>(); to IConfiguration. The idea is that T will be added as an argument to the Resolve methods generated and those arguments being used to resolve types of T. I’ve yet to figure out if adding a new property for these in ResolverMetadata or adding them to ResolverMetadata.Bindings and adding a new binder or something else is the right way to do this. This change is backwards-compatible and should work well with DependsOn.

The other api change adds generic argument TContextArgs to Setup and adds TContextArgs Args {get;} to IContext. This change requires IConfiguration, IBinding etc. to become generic on TContextArgs. An non-generic overload Setup() => Setup<Unit>() is provided to avoid breaking changes. The generated Resolve methods will take TContextArgs as first argument unless it is Unit and that argument will then be available in the ctx argument in To<> methods. This change will not play nice with DependsOn unless TContextArg is the same for all dependencies. When I started work on this I hadn’t understood that ctx mostly (only) acts as a placeholder and isn’t actually available at runtime, so it’s possible that it’s never going to work.

Three of the solutions are using the project as-is and are reproduced in the following:

using System;
using Pure.DI;

namespace AppStart.Pure_DI;


public struct Identity {
    public int UserId;
    // More fields
}

public interface IIdentityService {
    Identity Identity { get; }
}

public class IdentityService: IIdentityService {
    public IdentityService(Identity identity) {
        Identity=identity;
    }

    public Identity Identity { get; }
}


internal class SomeService {
    public SomeService(IIdentityService identityService) {
        IdentityService=identityService;
    }

    public IIdentityService IdentityService { get; }
}

public static class Test {
    public static void Run() {
        Run(new Identity { UserId = 42 });
    }

    public static void Run(Identity ident) {
        // This fails at compile-time. See details in bindings
        SomeService s1 = ComposerX.Resolve<Func<Identity, WithIdentityResolver>>()(ident).Resolve<SomeService>();


        // This fails at run-time. See details in bindings
        SomeService s2 = ComposerX.Resolve<Func<Identity, GenericCompositionRootWithIdentity<SomeService>>>()(ident).Value;

        SomeService s3 = ComposerX.Resolve<Func<Identity, SpecializedCompositionRootWithIdentity>>()(ident).SomeService;
    }
}


class WithIdentityResolver {
    public WithIdentityResolver(IContext resolverContext) {
        ResolverContext=resolverContext;
    }

    public IContext ResolverContext { get; }

    public T Resolve<T>() {
        return ResolverContext.Resolve<T>();
    }
}


internal class GenericCompositionRootWithIdentity<T> {
    public GenericCompositionRootWithIdentity(T value) {
        Value=value;
    }

    public T Value { get; }
}


internal class SpecializedCompositionRootWithIdentity {
    public SpecializedCompositionRootWithIdentity(SomeService someService) {
        SomeService=someService;
    }

    public SomeService SomeService { get; }
}


static partial class ComposerX {

    class CachedService<TService> {
        public TService Service { get; set; }
    }

    static ComposerX() =>
        // out=C:\tmp
        // verbosity=Diagnostic
        DI.Setup()
            .Default(Lifetime.PerResolve)
            .Bind<CachedService<TT>>().To<CachedService<TT>>()
            .Bind<Func<Identity, IdentityService>>().To(ctx => new Func<Identity, IdentityService>(ident =>
                ctx.Resolve<CachedService<IdentityService>>().Service = new(ident)
            ))
            .Bind<IIdentityService>().To(ctx => ctx.Resolve<CachedService<IdentityService>>().Service)
            .Bind<SomeService>().To<SomeService>()

            // Doesn't work because Funcs with generic return values can't be found
            // Cannot resolve an instance System.Func`2[AppStart.Pure_DI.Identity,AppStart.Pure_DI.GenericCompositionRootWithIdentity`1[AppStart.Pure_DI.SomeService]], consider adding it to the DI setup.
            .Bind<Func<Identity, GenericCompositionRootWithIdentity<TT>>>().To(ctx =>
                new Func<Identity, GenericCompositionRootWithIdentity<TT>>(ident => {
                    // This Func resolve ensures that IdentityService is cached for the current resolve
                    ctx.Resolve<Func<Identity, IdentityService>>()(ident);
                    return new(ctx.Resolve<TT>());
                }))

            // Doesn't work because resolves on ctx are rebound and ctx itself is not available in the rewritten Func
            //  error CS0103: The name 'ctx' does not exist in the current context
            //[System.Runtime.CompilerServices.MethodImplAttribute((System.Runtime.CompilerServices.MethodImplOptions)768)]private static System.Func<AppStart.Pure_DI.Identity, AppStart.Pure_DI.WithIdentityResolver> GetPerResolveSystemFuncAppStartPure_DIIdentityAppStartPure_DIWithIdentityResolver(){if( _perResolveSystemFuncAppStartPure_DIIdentityAppStartPure_DIWithIdentityResolver==default(System.Func<AppStart.Pure_DI.Identity, AppStart.Pure_DI.WithIdentityResolver>)){ _perResolveSystemFuncAppStartPure_DIIdentityAppStartPure_DIWithIdentityResolver=                new Func<Identity,WithIdentityResolver>(ident => {
            //( GetPerResolveSystemFuncAppStartPure_DIIdentityAppStartPure_DIIdentityService())(ident);
            //                    return new(ctx);
            //            });
            .Bind<Func<Identity, WithIdentityResolver>>().To(ctx =>
                new Func<Identity, WithIdentityResolver>(ident => {
                    // This Func resolve ensures that IdentityService is cached for the current resolve
                    ctx.Resolve<Func<Identity, IdentityService>>()(ident);
                    return new WithIdentityResolver(ctx);
                }))

            // Does work but will cause binding bloat
            .Bind<Func<Identity, SpecializedCompositionRootWithIdentity>>().To(ctx =>
                new Func<Identity, SpecializedCompositionRootWithIdentity>(ident => {
                    // This Func resolve ensures that IdentityService is cached for the current resolve
                    ctx.Resolve<Func<Identity, IdentityService>>()(ident);
                    return new(ctx.Resolve<SomeService>());
                }))


            ;
}

I’m not really sure where to go from here, so any helpful would be appriciated.

Cheers, John

Issue Analytics

  • State:closed
  • Created a year ago
  • Comments:23 (16 by maintainers)

github_iconTop GitHub Comments

1reaction
johnjuuljensencommented, Oct 17, 2022

This is great. Arg is a much better name ofcourse and obvious in hindsight 😃 I also tested it with DependsOn and the args propagate nicely to the dependant Resolve methods. Very cool.

0reactions
NikolayPianikovcommented, Oct 1, 2022

Please try 1.1.54, the sample is here.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Strategies to Resolve Conflict over Deeply Held Values
Strategies to resolve conflict over sacred values are particularly needed in our polarized world. 3 strategies help negotiators compromise.
Read more >
Assign value from successful promise resolve to external ...
The then() function returns the promise with a resolved value of the previous then() callback, allowing you the pass the value to subsequent ......
Read more >
7 Conflict Resolution Skills (and How To Use Them at Work)
Learn about 10 conflict resolution skills that can help you to de-escalate a situation and help your team reach a resolution and move ......
Read more >
How to Resolve a Values Conflict
Having a meaningful and respectful discussion with others impacted may be the simplest way to resolve it.
Read more >
27 Conflict Resolution Skills to Use with Your Team and ...
In this blog post, we'll review different conflict management skills and conflict resolution strategies you can use to navigate a conflict ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found