Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to configure simple injector container and lifestylse in a MVC web app with WebAPI, WCF, SignalR and Background Task

The simple injector documentation provides great examples on how to setup the container for WebRequest, Web API, WCF, ... but the examples are specific to one technology/lifestyle at a time. Our web application uses most of them together! It is not clear to me how to configure the container to work with several lifestyles.


Let's say I have a MVC project with Web API. I have the following objects:

  • MyDbContext : My entity code first db context
  • IMyDataProvider implemented by MyDataProvider : Contains query logic and uses MyDbContext
  • MyController : MVC controller that uses IMyDataProvider
  • MyApiController : WebApi controller that uses IMyDataProvider

Should I create and configure one container for each type of lifestyle ?

When I register everything with RegisterPerWebRequest<T> is works in both types of controllers. Is this safe ? Or will I run into trouble when using async/await in a Web API controller?

What is the best configuration when I have both MVC and Web API controllers who get injected the same instances ?

Should I use a hybrid lifestyle ?


Now to complicate things... our application also uses background tasks and SignalR.

Both of these will sometimes occur outside of a WebRequest and need access to the same objects as described above.

The best solution would be to use a Lifetime scope ?

Would I need to create a new container for that lifestyle? or can I reuse/reconfigure my MVC/Web API container ?

Is there a triple lifestyle?

like image 987
Chris Avatar asked Oct 17 '14 20:10

Chris


2 Answers

I have to say, I stumble on a similar scenario some time ago, I ended up by sharing my configuration over my web API and signalR, but you need to implement a custom lifestyle for signalR since it's not based on web request.

specially in signalR you'll find some issues handling per-web-request dependencies in a Hub some of them are going to be null like httpContext.Current among others.

The solution:

You need a hybrid lifestyle between WebRequestLifestlye and either Lifestyle.Transient, Lifestyle.Singleton, or LifetimeScopeLifestyle. I ended up I finished using the decorator pattern, you may read this post and this other post.

my decorator

public class CommandLifetimeScopeDecorator<T> : ICommandHandler<T>
    {
        private readonly Func<ICommandHandler<T>> _handlerFactory;
        private readonly Container _container;

        public CommandLifetimeScopeDecorator(
        Func<ICommandHandler<T>> handlerFactory, Container container)
        {
            _handlerFactory = handlerFactory;
            _container = container;
        }

        public void Handle(T command)
        {
            using (_container.BeginLifetimeScope())
            {
                var handler = _handlerFactory(); // resolve scoped dependencies
                handler.Handle(command);
            }
        }

    }

    public interface ICommandHandler<in T>
    {
        void Handle(T command);
    }

I managed the dependencies using a hub activator for signalR

public class MyHubActivator : IHubActivator
    {
        private readonly Container _container;

        public MyHubActivator(Container container)
        {
            _container = container;
        }

        public IHub Create(HubDescriptor descriptor)
        {
            return _container.GetInstance(descriptor.HubType) as IHub;
        }
    }

a composite root file which is where you are going to handle your dependencies

public CompositRoot(Container container)
{
    _container = container;
}
public container Configure()
{
   // _container.Registerall container dependencies
   return _container;
}

then share your composite root configuration when you are bootstrapping your app

var compositRoot = new CompositRoot(simpleInjector.Container); //simple injector instance
compositRoot.Configure();

For signalR

GlobalHost.DependencyResolver.Register(typeof(IHubActivator), () => new MyHubActivator(compositRoot));

and you may reuse your configuration among other projects!

my two cents hope that helps!

like image 191
pedrommuller Avatar answered Oct 09 '22 01:10

pedrommuller


Usually you don't need to have one container per lifestyle; In general you want to have one container instance per AppDomain. However, mixing Web API in the same project with MVC is from an architectural point of view a horrible idea IMO (as explained here, here, and here). So in case you are separating those parts into their own architectural blocks, you will already have less problems already.

But in case you are running MVC and Web API in the same project, this basically means that you will always be using Web API. The WebApiRequestLifestyle was explicitly built to work:

well both inside and outside of IIS. i.e. It can function in a self-hosted Web API project where there is no HttpContext.Current. (source)

In general, it is safe to use the WebRequestLifestyle in case you are only running in IIS when you have no intention to spin of parallel operations using ConfigureAwait(false) (which should be really rare IMO) as explained here.

So in the case you are still mixing Web API with MVC in the same project, there's no reason to use a hybrid lifestyle; you can simply use the same lifestyle. For doing background processing you might however need to build a hybrid lifestyle, but it every scenario needs a different hybrid. However, hybrids can be stacked up and you can easily create a 'triple lifestyle' if needed.

Since you want to do background processing with SignalR, you need to decide in what type of scoped lifestyle to run those background operations. The most obvious lifestyle is the LifetimeScopeLifestyle and this means you should make your scoped registrations using the following scoped lifestyle:

var hybridLifestyle = Lifestyle.CreateHybrid(
    lifestyleSelector: () => HttpContext.Current != null,
    trueLifestyle: new WebRequestLifestyle(),
    falseLifestyle: new LifetimeScopeLifestyle());

A lifetime scope however needs to be started explicitly (as were the web request scope gets started implicitly for you if you include the SimpleInjector.Integration.Web.dll in your web application). How to do this depends on your design, but this q/a about SignalR might point you in the right direction.

like image 44
Steven Avatar answered Oct 09 '22 02:10

Steven