I am working on a large project where the DB has many tables which are accessed in different modules for different reasons. So came up with the idea of using the Service Manager to point to Factory classes that would instantiate the TableGateway models and return them on request, effectively lazy loading the TableGateway model.
However I am still unsure about whether or not the Factories in the ServiceManager are lazy loaded or they're instantiated with the ServiceManager?
I.e., if I have something like this in my configuration file
array(
'service_manager' => array(
'factories => array(
'giftcard_table' => 'Giftcard\Factory\GiftcardTableFactory',
),
),
);
will a new instance of Giftcard\Factory\GiftcardTableFactory be created every time I call $sm->get('giftcard_table')? Or is this something that gets instantiated along with the ServiceManager at the beginning of every HTTP request regardless of whether I call $sm->get('giftcard_table') or not?
The factories will be instantiated when you request them via the service manager's get method. This method calls the create method, which in turn calls the createFromFactory method in the case of requesting a factory. As per the highlighted line on the last link, you can see that it does indeed instantiate the factory at the time it is requested. What you can also see is that it stores the factory. This is because services fetched from the service manager are shared by default. Shared means that the same instance will be returned on subsequent requests. You can think of it like a cache. You can disable this if you so desire, such that an instance of a given service will be created on every request.
In a configuration file:
// Some configuration omitted
'service_manager' => array(
/* ... */
'shared' => array(
'MyFactory' => false,
)
);
By accessing the service manager object:
$service_manager->setShared('MyFactory', false);
Some time ago I wrote an article about the service manager, which includes a section about the concept of shared services (near the bottom), which may be useful for you.
Related
I have a base abstract context which has a couple hundred shared objects, and then 2 "implementation" contexts which both inherit from the base and are designed to be used by different tenants in a .net core application. A tenant object is injected into the constructor for OnConfiguring to pick up which connection string to use.
public abstract class BaseContext : DbContext
{
protected readonly AppTenant Tenant;
protected BaseContext (AppTenant tenant)
{
Tenant = tenant;
}
}
public TenantOneContext : BaseContext
{
public TenantOneContext(AppTenant tenant)
: base(tenant)
{
}
}
In startup.cs, I register the DbContexts like this:
services.AddDbContext<TenantOneContext>();
services.AddDbContext<TenantTwoContext>();
Then using the autofac container and th Multitenant package, I register tenant specific contexts like this:
IContainer container = builder.Build();
MultitenantContainer mtc = new MultitenantContainer(container.Resolve<ITenantIdentificationStrategy>(), container);
mtc.ConfigureTenant("1", config =>
{
config.RegisterType<TenantOneContext>().AsSelf().As<BaseContext>();
});
mtc.ConfigureTenant("2", config =>
{
config.RegisterType<TenantTwoContext>().AsSelf().As<BaseContext>();
});
Startup.ApplicationContainer = mtc;
return new AutofacServiceProvider(mtc);
My service layers are designed around the BaseContext being injected for reuse where possible, and then services which require specific functionality use the TenantContexts.
public BusinessService
{
private readonly BaseContext _baseContext;
public BusinessService(BaseContext context)
{
_baseContext = context;
}
}
In the above service at runtime, I get an exception "No constructors on type 'BaseContext' can be found with the constructor finder 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder'". I'm not sure why this is broken....the AppTenant is definitely created as I can inject it other places successfully. I can make it work if I add an extra registration:
builder.RegisterType<TenantOneContext>().AsSelf().As<BaseContext>();
I don't understand why the above registration is required for the tenant container registrations to work. This seems broken to me; in structuremap (Saaskit) I was able to do this without adding an extra registration, and I assumed using the built in AddDbContext registrations would take care of creating a default registration for the containers to overwrite. Am I missing something here or is this possibly a bug in the multitenat functionality of autofac?
UPDATE:
Here is fully runable repo of the question: https://github.com/danjohnso/testapp
Why is line 66 of Startup.cs needed if I have lines 53/54 and lines 82-90?
As I expected your problem has nothing to do with multitenancy as such. You've implemented it almost entirely correctly, and you're right, you do not need that additional registration, and, btw, these two (below) too because you register them in tenant's scopes a bit later:
services.AddDbContext<TenantOneContext>();
services.AddDbContext<TenantTwoContext>();
So, you've made only one very small but very important mistake in TenantIdentitifcationStrategy implementation. Let's walk through how you create container - this is mainly for other people who may run into this problem as well. I'll mention only relevant parts.
First, TenantIdentitifcationStrategy gets registered in a container along with other stuff. Since there's no explicit specification of lifetime scope it is registered as InstancePerDependency() by default - but that does not really matter as you'll see. Next, "standard" IContainer gets created by autofac's buider.Build(). Next step in this process is to create MultitenantContainer, which takes an instance of ITenantIdentitifcationStrategy. This means that MultitenantContainer and its captive dependency - ITenantIdentitifcationStrategy - will be singletons regardless of how ITenantIdentitifcationStrategy is registered in container. In your case it gets resolved from that standard "root" container in order to manage its dependencies - well, this is what autofac is for anyways. Everything is fine with this approach in general, but this is where your problem actually begins. When autofac resolves this instance it does exactly what it is expected to do - injects all the dependencies into TenantIdentitifcationStrategy's constructor including IHttpContextAccessor. So, right there in the constructor you grab an instance of IHttpContext from that context accessor and store it for using in tenant resolution process - and this is a fatal mistake: there's no http request at this time, and since TenantIdentitifcationStrategy is a singleton it means that there will not ever be one for it! So, it gets null request context for the whole application lifespan. This effectively means that TenantIdentitifcationStrategy will not be able to resolve tenant identifier based on http requests - because it does not actually analyze them. Consequently, MultitenantContainer will not be able to resolve any tenant-specific services.
Now when the problem is clear, its solution is obvious and trivial - just move fetching of request context context = _httpContextAccessor.HttpContext to TryIdentifyTenant() method. It gets called in the proper context and will be able to access request context and analyze it.
PS. This digging has been highly educational for me since I had absolutely no idea about autofac's multi-tenant concept, so thank you very much for such an interesting question! :)
PPS. And one more thing: this question is just a perfect example of how important well prepared example is. You provided very good example. Without it no one would be able to figure out what the problem is since the most important part of it was not presented in the question - and sometimes you just don't know where this part actually is...
When registering an object with Autofac and resolving in middleware and again in the MVC middleware via a controller, the instance is different. Instances are registered per lifetime scope.
I've popped a repro project here https://github.com/jakkaj/AutofacResolveIssue.
The app sets a value to the IUserService in the middleware, then try to read that value in the ValuesController later.
This same technique worked in older versions of ASP.NET with autofac. Any ideas what's going on?
Okay, so I made the mistake of injecting my dependency in to the constructor of the middleware.
You should inject dependencies into the Invoke.
The entire middleware object is singleton across all app instances!
Change builder registration to use SingleInstance() insted of InstancePerLifetimeScope(), So that every dependent component or call to Resolve() gets the same, shared instance.
builder.RegisterAssemblyTypes(typeof(UserService).GetTypeInfo().Assembly)
.Where(t => t.Name.EndsWith("Service") || t.Name.EndsWith("Repo"))
.AsImplementedInterfaces()
.SingleInstance();
Single Instance This is also known as ‘singleton.’ Using single instance scope, one instance is returned from all requests in
the root and all nested scopes. When you resolve a single
instance component, you always get the same instance no matter where
you request it.
Check the link for more details
http://docs.autofac.org/en/latest/lifetime/instance-scope.html#single-instance
So I have a fairly comprehensive activity-based access control system I built for a web app under MVC 4 using Entity Framework. Well, to be precise the access control doesn't care if it's using EF or not, but the app is.
Anyway, I'm loading the user's permissions on each request right now. I get a reference to my DbContext injected from the IoC container into my ApplicationController, and it overrides OnAuthorization to stuff the user's profile into the HttpContext.Current.Items. Seems to work fairly well, but I can't help but wonder if it's the best way.
My thought was that since the users' permissions don't change often, if ever, the better way to do it would be to load the profile of permissions into the Session instead, and then not have to change them at all until the user logs out and logs back in (pretty common in desktop OS's anyway). But I'm concerned that if I fetch using the DbContext, then the object I get back is a dynamic proxy which holds a reference to the DbContext and I certainly don't want to do that for the whole session.
Thoughts? Is this a good approach, and if so how do I ensure that my DbContext doesn't linger beyond when I really need it?
Invoke .AsNoTracking() on the Set<UserPermission> before you query out. Entities will still be proxied, but will be detached from the DbContext.
var userPermission = dbContext.Set<UserPermission>().AsNoTracking()
.SingleOrDefault(x => x.UserName == User.Identity.Name);
Thoughts? Is this a good approach?
Putting a dynamically proxied entity in session will break as soon as you load balance your code across more than 1 web server. Why? Because of the dynamic proxy class. Server A understands the type DynamicProxies.UserPermission_Guid, because it queried out the entity. However Server B through N do not, and therefore cannot deserialize it from the Session. The other servers will dynamically proxy the entity with a different GUID.
That said, you could DTO your data into a POCO object and put it in session instead. However then you do not need to worry about your entity being attached to the context when you first query it out. AsNoTracking will only make the query perform a bit faster.
// you can still call .AsNoTracking for performance reasons
var userPermissionEntity = dbContext.Set<UserPermission>().AsNoTracking()
.SingleOrDefault(x => x.UserName == User.Identity.Name);
// this can safely be put into session and restored by any server with a
// reference to the DLL where the DTO class is defined.
var userPermissionSession = new UserPermissionInSession
{
UserName = userPermissionEntity.UserName,
// etc.
};
Thoughts? Is this a good approach?
Another problem attached to this approach is when you use the common pattern that create one dbContext per http request. This pattern typically dispose dbContext when the request ends.
protected virtual void Application_EndRequest(object sender, EventArgs e)
But what happen when we try to get navigation property of a proxy entity which reference to a disposed DbContext?
We will get a ObjectDisposedException
I am using Wcf in Sharp Architecture. I have configured my project following northwind sample using WcfSessionStorage etc. I have a method in the wcf service that gets a list of business object using Repository<>.GetAll(). I am testing the service method using WcfTestClient. When the method is called the very first time, everything works fine. However on the subsequent call, I get the following exception on the Repository<>.GetAll() method
[System.ObjectDisposedException]
Session is closed!
Object name: 'ISession'
It seems like the NHibernate session gets disposed after each call. I have got around this problem by decorating my service with the following attribute
[ServiceBehavior( InstanceContextMode = InstanceContextMode.PerCall )]
public class WcfService : IWcfService
{
}
However this means, an instance of the service will be created on each call that in turn will create a new nhibernate session etc. In my scenario there is no need of creating a new service instance per call and I think its an expensive process and not the right solution. I would like to know what is the best practice in my scenario and how to get this thing work with creating a new service instace per call.
Thanks
Nabeel
The easiest way is to create a new instance every time and it's not an expensive process, because creating a new object in .NET is like 0.00000000000000001 second (I read that on Ayande's blog or somewhere).
I use Autofac DI in my projects and I usually make ISession as container scoped (one per request). And then every class that uses (directly or indirectly) ISession has to be container scoped or lower (factory scoped == every class usage get's a new instance). If a class that uses ISession is higer scoped (session scoped == singleton) you'll run into problems that you currently have.
If your service is singleton service:
At first run the service is created, this service uses ISession, which should be container scoped, and it is on the first run.
The next request to service (service is now created) has still a reference to created ISession (which was closed on previous end request), so now it's closed.
I don't recomend using the same ISession that you'll open/close (it's not recomended in the NHibernate documentation), just use container scoped (I do and I don't have any performance issues), or you should create ISession manually in every method in your service like:
using(ISession s = ISessionFactory.OpenSession())
using(ITransaction t = .....)
....
But that isn't nice at all...
Please take a look at my answer to my own similar question: WCF/S#arpArch: underlying ISession is closed after the first call within a request.
#dmonlord is right that the creation of additional session instances within the same request is very cheap in this case.
Does anyone have any tips or best practices regarding how Autofac can help manage the NHibernate ISession Instance (in the case of an ASP.NET MVC application)?
I'm not overly familiar with how NHibernate sessions should be handled. That said, Autofac have excellent instance lifetime handling (scoping and deterministic disposal). Some related resources are this article and this question. Since you're in ASP.Net MVC land make sure you also look into the MVC integration stuff.
To illustrate the point, here's a quick sample on how you can use Autofac factory delegates and the Owned generic to get full control over instance lifetime:
public class SomeController
{
private readonly Func<Owned<ISession>> _sessionFactory;
public SomeController(Func<Owned<ISession>> sessionFactory)
{
_sessionFactory = sessionFactory;
}
public void DoSomeWork()
{
using (var session = _sessionFactory())
{
var transaction = session.Value.BeginTransaction();
....
}
}
}
The container setup to get this to work is quite simple. Notice that we don't have to do anything to get the Func<> and Owned<> types, these are made available automatically by Autofac:
builder.Register(c => cfg.BuildSessionFactory())
.As<ISessionFactory>()
.SingleInstance();
builder.Register(c => c.Resolve<ISessionFactory>().OpenSession());
Update: my reasoning here is that, according to this NHibernate tutorial, the lifetime of the session instance should be that of the "unit of work". Thus we need some way of controlling both when the session instance is created and when the session is disposed.
With Autofac we get this control by requesting a Func<> instead of the type directly. Not using Func<> would require that the session instance be created upfront before the controller instance is created.
Next, the default in Autofac is that instances have the lifetime of their container. Since we know that we need the power to dispose this instance as soon as the unit of work is done, we request an Owned instance. Disposing the owned instance will in this case immediately dispose the underlying session.
Edit: Sounds like Autofac and probably other containers can scope the lifetime correctly. If that's the case, go for it.
It isn't a good idea to use your IoC container to manage sessions directly. The lifetime of your session should correspond to your unit of work (transaction boundary). In the case of a web application, that should almost certainly be the lifetime of a web request.
The most common way to achieve this is with an HttpModule that both creates your session and starts your transaction when a request begins, then commits when the request has finished. I would have the HttpModule register the session in the HttpContext.Items collection.
In your IoC container, you could register something like HttpContextSessionLocator against ISessionLocator.
I should mention that your generic error handling should locate the current session and roll back the transaction automatically, or you could end up committing half a unit of work.