简体   繁体   中英

Injecting into a different project/assembly

I've been at this subject for hours now, so I decided to write and ask for some help.

There are many answers already on this forum that I can identify as being solutions to my problem, but I'm just not able to connect all the dots inside my head; and I'm not getting it done.

I'm trying to change injector on a big MVC application, from StructureMap to SimpleInjector.

I need to correctly pass the current user of the HttpContext to another project on the same solution holding all the repositories and the DBContext so it can be used/written when I do some CRUD operations.

I clearly didn't understand how StructureMap was wired; I found it complicated. I had this on the MVC project (can provide more info, because there a dozen of classes using StructureMap):

public class GlobalRegistry : StructureMap.Registry
{
    public GlobalRegistry()
    {
        For<INotificationRepository>().Use<NotificationRepository>();
        ...

and on the repositories project/class:

public class NotificationRepository : BaseRepository,INotificationRepository
{
    public NotificationRepository(string userContext) : base(userContext) { }

(...by magic...) the constructor would take userContext parameter to use later within the called method.

After replacement with SimpleInjector, I don't understand how this parameter gets injected.

For testing purposes, this works:

container.Register<INotificationRepository>(() => new NotificationRepository("username"), 
    Lifestyle.Singleton);

I read that I'm not supposed to inject HttpContext in the constructor, as it is a runtime variable and I understand why.

Next I tried a IUserContextFactory , but it didn't work either.

On the same MVC project, I have his class:

public static class ObjectFactory
{
    private static SimpleInjector.Container _container;

    public static void SetContainer(Container container)
    {
        ObjectFactory._container = container;
    }

    public static T GetInstance<T>() where T : class
    {
        return _container.GetInstance<T>();
    }
}

I'm using this class to store the container after container.Verify();

ObjectFactory.SetContainer(container);

On any MVC controller I use it like this:

IUserContext ctxUser = ObjectFactory.GetInstance<IUserContext>();

I've also tried something like the following on the repository throughout my attempts, but I always end up with a null UserName .

public NotificationRepository(IUserContext userContext) : base(userContext) { }

(common interface between MVC project and the repository project)

public interface IUserContext
{
    string Username { get; set; }
}

More important than knowing the solution, I would like to understand how the solution works and overcome the difficulty I've been having for the past few hours trying to understand and solve this.

Passing a run-time primitive value as a parameter to the constructor is not directly available in Simple Inject. Instead you can inject a component that allows you to get that value at run-time, so your approach with IUserContext seems the good way to go, it should work. Modify your class to add that component in your constructor, instead of the userName string. Register the new component and let you container inject it automatically when calling the constructor.

A sample implementation:

class HttpSessionUserContext : IUserContext 
{
    //Your specific implementation of getting the user name from your context
    public string CurrentUserName => (string)HttpContext.Session["userName"];
}

The registration:

container.Register<IUserContext, HttpSessionUserContext>(Lifestyle.Scoped);
container.Register<INotificationRepository, NotificationRepository> (Lifestyle.Scoped);

Here you have more info about the reason why passing primitive run-time parameters to the constructors is not implemented in Simple Inject.

About Lifestyle scopes: You probably shouldn't use Lifestyle.Singleton as your scope for this component, as it would get instantiated only once and reused as a singleton. In web apps you usually want to apply a Per-HttpRequest scope. You can do it like this: after creating your container define its default scope as WebRequestLifestyle , or WebApiRequestLifestyle :

var container = new Container();
container.Options.DefaultScopedLifestyle = new WebRequestLifestyle();

And then when you register your components use the value Lifestyle.Scoped , which will apply the default scoped Lifestyle:

container.Register<SomeInterface, SomeClass>(Lifestyle.Scoped);

Edit: as per Steven 's comment, in this case it would be better registering HttpSessionUserContext as Singleton , because it is stateless. In general, Singleton has a better performance because it is only instantiated once and shared, but be careful with components that are not stateless or have dependencies to other components.

Also, make sure that you registered your MVC controllers and assigned the instance of your container to your MVC DependencyResolver . This is what really enables the automatic resolving and injection of the parameters in the constructors in the controllers. I guess you did this in your Application_Start event handler.

container.RegisterMvcControllers(Assembly.GetExecutingAssembly());
container.Verify();
DependencyResolver.SetResolver(
        new SimpleInjectorDependencyResolver(container));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM