Simple injector: insert the same instance of UnitOfWork instance through services of the same graph

I have several services, each of which has UnitOfWork introduced into the constructor using the Simple Injector IoC container.

Currently, I see that each instance of UnitOfWork is a separate object, which is bad because I use the Entity Framework and require the same contextual reference in all units of work.

How can I ensure that the same instance of UnitOfWork injected into all services for each permission request? My UnitOfWor will be saved by an external command handler decoder after the command completes.

Please note that this is a regular library and will be used for both MVC and Windows Forms, it would be nice to have a common solution for both platforms, if possible.

Code below:

 // snippet of code that registers types void RegisterTypes() { // register general unit of work class for use by majority of service layers container.Register<IUnitOfWork, UnitOfWork>(); // provide a factory for singleton classes to create their own units of work // at will container.RegisterSingle<IUnitOfWorkFactory, UnitOfWorkFactory>(); // register logger container.RegisterSingle<ILogger, NLogForUnitOfWork>(); // register all generic command handlers container.RegisterManyForOpenGeneric(typeof(ICommandHandler<>), AppDomain.CurrentDomain.GetAssemblies()); container.RegisterDecorator(typeof(ICommandHandler<>), typeof(TransactionCommandHandlerDecorator<>)); // register services that will be used by command handlers container.Register<ISynchronisationService, SynchronisationService>(); container.Register<IPluginManagerService, PluginManagerService>(); } 

The desired result of the following line is to create an object that has a common instance of UnitOfWork over the entire constructed object graph:

 var handler = Resolve<ICommandHandler<SyncExternalDataCommand>>(); 

Here are my services:

 public class PluginManagerService : IPluginSettingsService { public PluginManagerService(IUnitOfWork unitOfWork) { this.unitOfWork = unitOfWork; } private readonly unitOfWork; void IPluginSettingsService.RegisterPlugins() { // manipulate the unit of work } } public class SynchronisationService : ISynchronisationService { public PluginManagerService(IUnitOfWork unitOfWork) { this.unitOfWork = unitOfWork; } private readonly unitOfWork; void ISynchronisationService.SyncData() { // manipulate the unit of work } } public class SyncExternalDataCommandHandler : ICommandHandler<SyncExternalDataCommand> { ILogger logger; ISynchronisationService synchronisationService; IPluginManagerService pluginManagerService; public SyncExternalDataCommandHandler( ISynchronisationService synchronisationService, IPluginManagerService pluginManagerService, ILogger logger) { this.synchronisationService = synchronisationService; this.pluginManagerService = pluginManagerService; this.logger = logger; } public void Handle(SyncExternalDataCommand command) { // here i will call both services functions, however as of now each // has a different UnitOfWork reference internally, we need them to // be common. this.synchronisationService.SyncData(); this.pluginManagerService.RegisterPlugins(); } } 
+8
c # dependency-injection simple-injector entity-framework-4
source share
1 answer

The registration required depends on the type of application. Since you are talking about two different environments (MVC and WinForms), both will have a different registration.

For an MVC application (or web applications in general), the most common task is to register a unit of work based on each web request . For example, the following registration will cache a unit of work during a single web request:

 container.Register<IUnitOfWork>(() => { var items = HttpContext.Current.Items; var uow = (IUnitOfWork)items["UnitOfWork"]; if (uow == null) { items["UnitOfWork"] = uow = container.GetInstance<UnitOfWork>(); } return uow; }); 

The disadvantage of this registration is that the unit of work is not located (if necessary). There is an extension package for a simple injector that adds the RegisterPerWebRequest extension to container methods that automatically guarantee that the instance is located at the end of the web request. Using this package, you can do the following registration:

 container.RegisterPerWebRequest<IUnitOfWork, UnitOfWork>(); 

This is a shortcut for:

 container.Register<IUnitOfWork, UnitOfWork>(new WebRequestLifestyle()); 

A Windows Forms application, on the other hand, is usually single-threaded (one user will use this application). I believe that it is not unusual to have a single work unit for each form that is closed by the form, but using the command / handler template, I find it better to use a more service-oriented approach. I mean, it would be nice to design it so that you can move the business layer to the WCF service without having to make changes to the presentation layer. You can achieve this by letting your teams contain only primitives and (other) DTOs . Therefore, do not store Entity Framework objects in your commands, because this will greatly complicate the serialization of the command, and this will lead to surprises later.

When you do this, it would be convenient to create a new unit of work before the command handler starts execution, reuses the same unit of work during the execution of this handler, and captures it when the handler successfully completes (and always disposes of it). This is a typical scenario for Lifestyle . There is an extension package that adds RegisterLifetimeScope extension methods to the container. Using this package, you can do the following registration:

 container.RegisterLifetimeScope<IUnitOfWork, UnitOfWork>(); 

This is a shortcut for:

 container.Register<IUnitOfWork, UnitOfWork>(new LifetimeScopeLifestyle()); 

However, registration is only half the story. The second part is to decide when you need to save changes in the unit of work, as well as in the case of using a life style of life, where to start and end on such a scale. Since you must explicitly start the entire scope before executing the command and end it when the command has finished executing, the best way to do this is to use a command handler handler that can wrap your command handlers. Therefore, for a Forms application, you usually register an additional command handler handler that controls the lifespan. In this case, this approach does not work. Look at the following decorator, but note that this is not true:

 private class LifetimeScopeCommandHandlerDecorator<T> : ICommandHandler<T> { private readonly Container container; private readonly ICommandHandler<T> decoratedHandler; public LifetimeScopeCommandHandlerDecorator(...) { ... } public void Handle(T command) { using (this.container.BeginLifetimeScope()) { // WRONG!!! this.decoratedHandler.Handle(command); } } } 

This approach does not work because the processed command handler is created before . The validity period starts.

We may be tempted to try to solve this problem as follows, but this is also not true:

 using (this.container.BeginLifetimeScope()) { // EVEN MORE WRONG!!! var handler = this.container.GetInstance<ICommandHandler<T>>(); handler.Handle(command); } 

Although the ICommandHandler<T> request within the context of the life area does insert IUnitOfWork for this area, the container returns a handler that is (again) decorated with LifetimeScopeCommandHandlerDecorator<T> . Calling handler.Handle(command) will result in a recursive call, and we will end the exception.

The problem is that the dependency graph is already built before we can run the area of ​​life. Therefore, we have to break the dependency graph, postponing the construction of the rest of the graph. The best way to do this in order to keep your application design clean] is to change the decorator to a proxy and insert a factory into it that will create the type that it should wrap. Such a LifetimeScopeCommandHandlerProxy<T> would look like this:

 // This class will be part of the Composition Root of // the Windows Forms application private class LifetimeScopeCommandHandlerProxy<T> : ICommandHandler<T> { // Since this type is part of the composition root, // we are allowed to inject the container into it. private Container container; private Func<ICommandHandler<T>> factory; public LifetimeScopeCommandHandlerProxy(Container container, Func<ICommandHandler<T>> factory) { this.factory = factory; this.container = container; } public void Handle(T command) { using (this.container.BeginLifetimeScope()) { var handler = this.factory(); handler.Handle(command); } } } 

Introducing the delegate, we can delay the time of creating the instance, and thereby we delay the construction (the rest of) of the dependency graph. The trick now is to register this proxy class in such a way that it will inject wrapped instances and not (of course) inject itself again. Simple Injector supports embedding Func<T> factories in decorators, so you can simply use RegisterDecorator , in which case even the RegisterSingleDecorator extension method.

Note that the order in which decorators are registered (and this proxy) matters. Since this proxy launches a new realm of life, it should wrap a decorator that performs a block of work. In other words, a more complete registration would look like this:

 container.RegisterLifetimeScope<IUnitOfWork, UnitOfWork>(); container.RegisterManyForOpenGeneric( typeof(ICommandHandler<>), AppDomain.CurrentDomain.GetAssemblies()); // Register a decorator that handles saving the unit of // work after a handler has executed successfully. // This decorator will wrap all command handlers. container.RegisterDecorator( typeof(ICommandHandler<>), typeof(TransactionCommandHandlerDecorator<>)); // Register the proxy that starts a lifetime scope. // This proxy will wrap the transaction decorators. container.RegisterSingleDecorator( typeof(ICommandHandler<>), typeof(LifetimeScopeCommandHandlerProxy<>)); 

IUnitOfWork , registering a proxy server and a decoder means that TransactionCommandHandlerDecorator<T> will depend on a different IUnitOfWork than the rest of the dependency graph, which will mean that all changes made to the unit of work in this graph will not be committed. In other words, your application will stop working. Therefore, carefully read this registration.

Good luck.

+20
source share

All Articles