Inject ISession into custom valueresolver
Asked Answered
I

2

0

I'm trying to inject an instance of ISession into a custom AutoMapper ValueResolver.

Here's the resolver

public class ContactTypeResolver 
    : ValueResolver<Common.Models.ContactType, Models.ContactType>
{
    ISession _session;

    public ContactTypeResolver(ISession session)
    {
        _session = session;
    }

    protected override Models.ContactType ResolveCore(Common.Models.ContactType source)
    {
        return _session.Load<Models.ContactType>(source.Id);
    }
}

I have a profile for setting up AutoMapper

this.CreateMap<Models.PhoneNumber, Common.Models.PhoneNumber>()
    .ReverseMap()
    .ForMember(d => d.Type, o => o.ResolveUsing<ContactTypeResolver>());

I register the resolver in a StructureMap registry like so

For<ValueResolver<Common.Models.ContactType, Models.ContactType>>()
   .Add<ContactTypeResolver>();

I am using session-per-request, and I am set the session inside of a nested container inside of StructureMapDependencyScope.cs which was created when I added StructureMap to my Web Api project. Here's the code

public void CreateNestedContainer()
{
    if (CurrentNestedContainer != null)
    {
        return;
    }
    CurrentNestedContainer = Container.GetNestedContainer();
    CurrentNestedContainer.Configure(c => c.For<ISession>().Use(ctx => ctx.GetInstance<ISessionFactory>().OpenSession()));

}

And this is how my container is set

var container = StructuremapMvc.StructureMapDependencyScope.Container;
GlobalConfiguration.Configuration.DependencyResolver = new StructureMapWebApiDependencyResolver(container);

container.Configure(x =>
{
    x.IncludeRegistry<DefaultRegistry>();
});

Mapper.Initialize(cfg =>
{
    cfg.ConstructServicesUsing(container.GetInstance);

    foreach (var profile in container.GetAllInstances<Profile>())
        cfg.AddProfile(profile);
});

I even tried using the service locator like so

this.CreateMap<Models.PhoneNumber, Common.Models.PhoneNumber>()
    .ReverseMap()
    .ForMember(d => d.Type, o => o
         .ResolveUsing<ContactTypeResolver>()
         .ConstructedBy(StructureMap.ObjectFactory.GetInstance<ContactTypeResolver>));

However, when the code runs I get a run-time exception stating

No default Instance is registered and cannot be automatically determined for type 'NHibernate.ISession'.

I also tried injecting another type registered in my container, which also did not work. What am I missing? The session instance does get injected into other objects. Also, other mappings that don't require dependency injection in the same profile work just fine.

Intertexture answered 6/5, 2015 at 15:44 Comment(0)
S
2

I believe @RadimKöhler is right.

Your parent container (where the AutoMapper types are configured) doesn't have access to the plugin types defined in the nested container. It just doesn't know about the ISession plugin type hence the exception you get.

I can think of a couple solutions here. DISCLAIMER I didn't tested them.

  1. Move the registration of the ISession type to your parent container. You should be able to scope it to HTTP requests there as well.

For example with the following registration code. I have no knowledge about ASP.NET WebAPI so there might be some differences but you get the point.

public class NHibernateRegistry : Registry
{
      public NHibernateRegistry()
      {
        For<Configuration>().Singleton().Use(c => new ConfigurationFactory().AssembleConfiguration());
        For<ISessionFactory>().Singleton().Use(c => c.GetInstance<Configuration>().BuildSessionFactory());
        For<ISession>().HybridHttpOrThreadLocalScoped().Use(c =>
        {
          var sessionFactory = c.GetInstance<ISessionFactory>();
          return sessionFactory.OpenSession();
        });
      }
    }
  1. Tell AutoMapper you want to use the nested container.
Sidestep answered 7/5, 2015 at 8:31 Comment(3)
For some reason, I thought structuremap.web and HybridHttpOrThreadLocalScoped was not to be used. Anyway, it's working perfectly...Intertexture
Btw, do I need CurrentNestedContainer.Configure(c => c.For<ISession>().Use(ctx => ctx.GetInstance<ISessionFactory>().OpenSession())); in my StructureMapDependencyScope.cs?Intertexture
@JoshC. The registration in the parent container should be enough. You can even get rid of your nested container if there are no other registrations.Sidestep
F
1

In general, I would say, that the issue with:

No default Instance is registered ... ISession

we should solve by registering ISession inside of our container:

x.For<ISession>()
 .Use...

What would be the use? It could be our own way how to retrieve the contextual ISession. There could be code like this:

x.For<ISession>()
 .Use(() => MySessionProvider.GetCurrentSession());

This will be enough for StructureMap to properly inject this kind of instance into our Web API Service.

Behind MySessionProvider.GetCurrentSession() could be some call to static internal API which returns ISession related to HttpContext (initiated and disposed at request Start and End)

Or we can follow this:

Check this part of doc/guidance as well:

Retrieving a Service from IContext

and the subsection:

Retrieving a Service from IContext, which shows that we can do it like this

You can also retrieve other services from the IContext during object construction. Because the underlying BuildSession manages the Auto Wiring, you can generally assume that you're using the exact same object instance for a PluginType that other objects in the same object graph will receive. That's a helpful feature when you're talking about using View's within any type of desktop application or any kind of NHibernate object where the state or identity of the object requested is important.

My team uses this functionality in our NHibernate bootstrapping. We have an interface named ISessionSource that is responsible for creating the NHibernate ISession objects (it wraps a Session).

public interface ISessionSource
{
    ISession CreateSession();
}

We can't just walk up and create an ISession object directly. Instead, you have to use the ISessionSource to create an ISession for you. We still want StructureMap to inject the ISession objects into other classes, so we use the IContext.GetService<ISession>() method from within a Lambda to build ISession objects:

ForRequestedType<ISession>().TheDefault.Is.ConstructedBy(
    context => context.GetInstance<ISessionSource>().CreateSession());
Fishbowl answered 6/5, 2015 at 18:5 Comment(3)
Thanks for the reply. I may have omitted how I'm creating the session per request. I've updated my question accordingly. Basically, I create a nested container where the lifecycle matches the http request.Intertexture
The point here is, that nested container has definition for ISession, but the outer/parent, which is creating Resolver does not have such registration. NOTE I am using StructureMap 3 - without these nested containers... Base on what I read, the point is that you should use the context.GetInstance.. not StructureMap.ObjectFactory.. check this aspzone.com/tech/…Encumber
I understand, but I'm still not quite sure what to do. The ISession from my nested container is getting injected everywhere except in my AutoMapper custom ValueResolver, and I don't know how to access the context in the lambda in my AM profile.Intertexture

© 2022 - 2024 — McMap. All rights reserved.