netcore 2.1 Distributed cache with Scoped repository - asp.net-core

Need some help.
I have a .netcore 2.1 API which is secured via Azure Bearer token from its clients. I am wanting to collect user information from the bearer token of clients and store it in a SQL database so that I can tag entries within the database if they are being added/deleted/edited etc. For the SQL table joins I therefore need the user imformation in SQL.
Below is my implementation of a Cache Service using IDistributedCache. On Init I am trying to load all currently stored users from the SQL DB in to the cache, then added to it when new users connect in.
To capture the connections across the entire API I was using a TypeFilterAttribute to execute OnActionExecuting.
The problem is that the CacheService is a singleton and is calling the UserRepository - which is scoped. This isn't allowed.
Any thoughts?
startup.cs
public void ConfigureServices(IServiceCollection services)
{
...
// Context
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.TryAddSingleton<CacheService>();
// Repositories
services.TryAddScoped<IUserRepository, UserRepository>();
services.AddDistributedMemoryCache();
services.AddMvc(
opts => opts.Filters.Add(new HttpInterceptor())
)
...
CacheService.cs
public class CacheService
{
private readonly IDistributedCache _cache;
private readonly IUserRepository _userRepository;
public CacheService(
IDistributedCache cache,
[FromServices] IUserRepository userRepository
)
{
_cache = cache;
_userRepository = userRepository;
// Populate cache from DB
var users = _userRepository.GetAll().Result;
foreach (var u in users)
{
if (_cache.GetAsync(u.Username).Result == null)
{
var profileSerialised = JsonConvert.SerializeObject(UserToUserProfile(u));
var entry = Encoding.UTF8.GetBytes(profileSerialised);
_cache.SetAsync(u.Username, entry, new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30) });
}
}
}
HttpInterceptor.cs
public class HttpInterceptor : TypeFilterAttribute
{
public HttpInterceptor() : base(typeof(IHttpInterceptor))
{
}
private class IHttpInterceptor : IActionFilter
{
private readonly CacheService _cache;
private readonly IUserRepository _userRepository;
public IHttpInterceptor(
CacheService cache,
IUserRepository userRepository)
{
_cache = cache;
_userRepository = userRepository;
}
public void OnActionExecuting(ActionExecutingContext context)
{
if (context.HttpContext.User.Identity.IsAuthenticated)
{
this._cache.GetUserProfile(context.HttpContext.User.Identity.Name);
}
}

First, you're looking at this upside-down and backwards. Having some service add stuff to the cache and then having other code just assume that stuff is in the cache ready to go is a recipe for disaster. Instead, have your dependent code literally request the data it needs, and then, if you want to cache it, do it the method that fetches the data. That way your app code remains agnostic about where the data is coming from; it just calls a method and it gets the data it wants. Under the hood, it's either pulled from the database or the cache, depending on which is available/preferred.
Your cache service has serious issues anyways. First, it should not be a singleton in the first place. There's no reason for it to be, and since you're dealing with scoped services inside, you're only making things more difficult than they need to be. Second, you should never ever utilize I/O in a constructor. Only simple variable/prop initialization should be done there. Anything that requires actual work should go into a method. If you truly want to do something on initialization, then you should implement a factory pattern. For example, you might have something like a CacheServiceFactory with a Create method that returns a fully instantiated CacheService including calling any methods that do actual work.
With the disclaimers aside, in general, to use a scoped service in a singleton, you must create a scope. This must be done every time you want to utilize the service; you cannot persist the service to an ivar on your singleton class. Simply, you inject IServiceProvider into your class, which is itself singleton-scoped, so you'll have no problems with that. Then, when you need to utilize a scoped service:
using (var scope = provider.CreateScope())
{
var repo = scope.ServiceProvider.GetRequiredService<IUserRepository>();
// do something with repo
}
This is called the service locator anti-pattern. It's called such because it's something you should really avoid doing. Sometimes that's no always possible. However, more often than not, you can simply design things in a different way: such as making the service scoped itself.

Related

Invalidating Cached Data and Dependency Injection Pattern

I have a data cache class (that uses the MemoryCache class).
The basic function of this class is to cache reference data. To get this reference data it needs an instance of an Entity Framework dbContext. This gets passed in by dependency injection (Simple Injector).
But this dbContext has a lifecycle of "per call" (AsyncScopedLifestyle). So to satisify this I put the call to setup the cache in a "scope" that expires after the call.
The cache gets invalidated every 2 hours and is re-queried. Unsurprisingly, the dbContext has been cleaned up by then (because it went out of the scope).
I can think of ways to get around this issue. But I want to know if there is a pattern I should be following for this kind of issue. (Most of my solutions have me passing the container into my cache class. But that seems to be a violation of several DI patterns.)
Anyone know of a design pattern to use when you have a reoccurring need for an injection inside of a class?
A bit more background:
My cache class (called DataCache) gets the context from constructor injection.
The call to set it up is made from the Configure method in Startup.cs. This looks like this:
.
using (AsyncScopedLifestyle.BeginScope(container))
{
// Setup the long lived data caching
var dataCache = container.GetInstance<DataCache>();
dataCache.SetupCachedItems();
}
It sets the MemoryCache to expire the data in the cache after two hours. But the injected context is long cleaned up by then.
I see two general solutions here:
Move the cache that the DataCache manages out of that class, in such way that MyCacheClass can become Scoped. This seams a no-brainer as this is likely what MemoryCache is for. Memory cache is likely a Singleton.
Move DataCache into the Composition Root so it can safely depend on the container (or a container abstraction), without falling into Service Locator anti-pattern trap.
The first solution can be applied in multiple ways. Perhaps it's a matter of defining the cache in a static field:
public class DataCache
{
private static ConcurrentDictionary<string, object> cache;
}
And in case you inject MemoryCache as storage provider for your data, it will contain the cache, and the lifestyle of DataCache becomes irrelevant:
public class DataCache
{
public DataCache(MyContext context, IMemoryCache cache)
}
If, however, DataCache needs to be injected into Singleton consumers, it itself needs to be Singleton. This disallows this approach, as MyContext needs to be Scoped, to prevent Captive Dependencies. For that you can use solution 2.
With solution to, you ensure that DataCache is created inside your Composition Root. This forces you to hide DataCache behind an abstraction, e.g. IDataCache. This abstraction can be placed in a location that allows consumers to depend on, while the DataCache implementation will be completely hidden inside the Composition Root. At that location it becomes safe to depend on the DI Container.
// Part of the Composition Root
sealed class DataCache: IDataCache
{
public DataCache(Container container, IMemoryCache cache) ...
public ProductData GetProductByKey(string key)
{
if (key not in cache)
{
using (AsyncScopedLifestyle.BeginScope(this.container))
{
var context = container.GetInstance<MyContext>();
var p = context.Products.SingleOrDefault(p => p.Key == key);
var data = new ProductData(p);
AddProductToCache(key, data);
return data;
}
}
}
}
You should rely on DI the whole way. In other words, if the cache class needs the context, then that's a dependency, and should be injected as such:
public class MyCacheClass
{
private readonly MyContext _context;
public MyCacheClass(MyContext context)
{
_context = context;
}
...
}
That of course assumes the cache class has a scoped lifetime as well, which there's really no reason it shouldn't, since it interacts with scoped dependencies. However, if for some reason you need it to have a singleton lifetime, then you can simply inject IServiceProvider and then create a scope and pull out the context when you need it:
using (var scope = _serviceProvider.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<MyContext>();
// do something with context
}
If you're using a static class, don't.

Confused about using the very same HTTPContextAccessor instance through the entire ASP.NET Core 3 application?

I've read about configuring IHttpContextAccessor as services.AddSingleton scope, but I also read about it is working "async local", and I am also aware of the sophisticated working of async in ASP.Net, I mean for example if a controller action method is async, and it await for an async call, then it may continue with an other thread, but magically some thread bound things with maintained (like HttpContext)
My concrete use case: I have to inject a MyConverter class to my EF Core DbContext which uses it in OnModelCreating. However this model is cached by the DbContext, so any subsequent request, even it will have a brand new instance of DbContext will use this very same model so the very same MyConverter instance. (even it has configured services.AddTransient). This MyConverter has a constructor and an injected IHttpContextAccessor, so based on the very similar reasons, it effectively will be also a singleton for all DbContext/MyConverter usages.
Question
This particular HttpContextAccessor instance which is created in the very first request will serve all the subsequent requests in the Web app lifecycle. Will it work correctly? Is there any (concurrency) trap here?
(Do I suppose correctly that it does not really matter if we use a single or multiple HttpContextAccessor instances, because its implementation of getting HttpContext will use the correct way including async local thread switch traps etc to return with the correct HttpContext?)
Short answer: register as services.AddHttpContextAccessor() and then you can inject IHttpContextAccessor wherever you want and it'll work as long as you're using it in the request's execution context. For instance you can't read HTTP request headers for code that was not initiated by a HTTP request.
You're right that the IHttpContextAccessor should be registered as a singleton. Instead of doing it yourself, the recommendation is to use AddHttpContextAccessor() extension method. See source code here. It internally registers an HttpContextAccessor as a singleton.
The code for HttpContextAccessor can be found here, which I'm also pasting below:
public class HttpContextAccessor : IHttpContextAccessor
{
private static AsyncLocal<HttpContextHolder> _httpContextCurrent = new AsyncLocal<HttpContextHolder>();
public HttpContext HttpContext
{
get
{
return _httpContextCurrent.Value?.Context;
}
set
{
var holder = _httpContextCurrent.Value;
if (holder != null)
{
// Clear current HttpContext trapped in the AsyncLocals, as its done.
holder.Context = null;
}
if (value != null)
{
// Use an object indirection to hold the HttpContext in the AsyncLocal,
// so it can be cleared in all ExecutionContexts when its cleared.
_httpContextCurrent.Value = new HttpContextHolder { Context = value };
}
}
}
private class HttpContextHolder
{
public HttpContext Context;
}
}
Since the HttpContext getter property returns from an async local field, you always get the HttpContext local to the execution context.
The HttpContext field is set in HttpContextFactory.Create() only if IHttpContextAccessor was registered with the DI. Source.
And HttpContextFactory.Create() is invoked from [HostingApplication](https://github.com/aspnet/AspNetCore/blob/v2.2.5/src/Hosting/Hosting/src/Internal/HostingApplication.cs) where the context is setup.

Autofac.Multitenant in an aspnet core application does not seem to resolve tenant scoped dependencies correctly

I'm in the process of upgrading a Multitenant dotnet core solution which utilises the Autofac.Multitenant framework. I'm not having a lot of luck getting tenancy resolution working correctly. I've created a simple demonstration of the problem here: https://github.com/SaltyDH/AutofacMultitenancy1
This repo demonstrates registering a InstancePerTenant scoped dependency TestMultitenancyContext which is resolved in the Home Controller. Due to issues with using IHttpContextAccessor, I'm using a custom RequestMiddleware class to capture the current HttpContext object so that I can perform logic on the current HttpContext request object in the MultitenantIdentificationStrategy.
Finally, TestFixture provides a simple xUnit test which, at least on my machine returns "tenant1" for both tenants.
Is there something I've missed here or is this just not currently working?
UPDATE 10/6/2017: We released Autofac.AspNetCore.Multitenant to wrap up the solution to this in a more easy to consume package. I'll leave the original answer/explanation here for posterity, but if you're hitting this you can go grab that package and move on.
I think you're running into a timing issue.
If you pop open the debugger on the HttpContext in the middleware you can see that there's a RequestServicesFeature object on a property called ServiceProvidersFeature. That's what's responsible for creating the per-request scope. The scope gets created the first time it's accessed.
It appears that the order goes roughly like this:
The WebHostBuilder adds a startup filter to enable request services to be added to the pipeline.
The startup filter, AutoRequestServicesStartupFilter, adds middleware to the very beginning of the pipeline to trigger the creation of request services.
The middleware that gets added, RequestServicesContainerMiddleware, basically just invokes the RequestServices property from the ServiceProvidersFeature to trigger creation of the per-request lifetime scope. However, in its constructor is where it gets the IServiceScopeFactory that it uses to create the request scope, which isn't so great because it'll be created from the root container before a tenant can be established.
All that yields a situation where the per-request scope has already been determined to be for the default tenant and you can't really change it.
To work around this, you need to set up request services yourself such that they account for multitenancy.
It sounds worse than it is.
First, we need a reference to the application container. We need the ability to resolve something from application-level services rather than request services. I did that by adding a static property to your Startup class and keeping the container there.
public static IContainer ApplicationContainer { get; private set; }
Next, we're going to change your middleware to look more like the RequestServicesContainerMiddleware. You need to set the HttpContext first so your tenant ID strategy works. After that, you can get an IServiceScopeFactory and follow the same pattern they do in RequestServicesContainerMiddleware.
public class RequestMiddleware
{
private static readonly AsyncLocal<HttpContext> _context = new AsyncLocal<HttpContext>();
private readonly RequestDelegate _next;
public RequestMiddleware(RequestDelegate next)
{
this._next = next;
}
public static HttpContext Context => _context.Value;
public async Task Invoke(HttpContext context)
{
_context.Value = context;
var existingFeature = context.Features.Get<IServiceProvidersFeature>();
using (var feature = new RequestServicesFeature(Startup.ApplicationContainer.Resolve<IServiceScopeFactory>()))
{
try
{
context.Features.Set<IServiceProvidersFeature>(feature);
await this._next.Invoke(context);
}
finally
{
context.Features.Set(existingFeature);
_context.Value = null;
}
}
}
}
Now you need a startup filter to get your middleware in there. You need a startup filter because otherwise the RequestServicesContainerMiddleware will run too early in the pipeline and things will already start resolving from the wrong tenant scope.
public class RequestStartupFilter : IStartupFilter
{
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
{
return builder =>
{
builder.UseMiddleware<RequestMiddleware>();
next(builder);
};
}
}
Add the startup filter to the very start of the services collection. You need your startup filter to run before AutoRequestServicesStartupFilter.
The ConfigureServices ends up looking like this:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.Insert(0, new ServiceDescriptor(typeof(IStartupFilter), typeof(RequestStartupFilter), ServiceLifetime.Transient));
services.AddMvc();
var builder = new ContainerBuilder();
builder.RegisterType<TestMultitenancyContext>().InstancePerTenant();
builder.Populate(services);
var container = new MultitenantContainer(new MultitenantIdentificationStrategy(), builder.Build());
ApplicationContainer = container;
return new AutofacServiceProvider(container);
}
Note the Insert call in there to jam your service registration at the top, before their startup filter.
The new order of operations will be:
At app startup...
Your startup filter will add your custom request services middleware to the pipeline.
The AutoRequestServicesStartupFilter will add the RequestServicesContainerMiddleware to the pipeline.
During a request...
Your custom request middleware will set up request services based on the inbound request information.
The RequestServicesContainerMiddleware will see that request services are already set up and will do nothing.
When services are resolved, the request service scope will already be the tenant scope as set up by your custom request middleware and the correct thing will show up.
I tested this locally by switching the tenant ID to come from querystring rather than host name (so I didn't have to set up hosts file entries and all that jazz) and I was able to switch tenant by switching querystring parameters.
Now, you may be able to simplify this a bit. For example, you may be able to get away without a startup filter by doing something directly to the web host builder in the Program class. You may be able to register your startup filter right with the ContainerBuilder before calling builder.Populate and skip that Insert call. You may be able to store the IServiceProvider in the Startup class property if you don't like having Autofac spread through the system. You may be able to get away without a static container property if you create the middleware instance and pass the container in as a constructor parameter yourself. Unfortunately, I already spent a loooot of time trying to figure out the workaround so I'm going to have to leave "optimize it" as an exercise for the reader.
Again, sorry this wasn't clear. I've filed an issue on your behalf to get the docs updated and maybe figure out a better way to do this that's a little more straightforward.
I have an alternate solution, related to work I've done on a pending PR on the Autofac DI extension. The solution there can't be used exactly, because it depends on classes that are (rightly) internal. It can be adapted by providing shims that reproduce the functionality in those classes. Since they are compact, this doesn't require the addition of a lot of code. Until the functionality is fixed, this is the solution I'm using.
The other aspect of the solution is to eschew the custom middleware and instead make the ITenantIdentificationStrategy a service that can take any dependency required to do what it needs to.
Fixing the DI
The "DI" side of the problem is that the Autofac DI extension uses resolution to supply IServiceProvider and IServiceScopeFactory implementations. This is possible, because under the hood these are IComponentContext and ILifetimeScope (which are themselves different interfaces for the same thing). In most cases this works fine, but ASP.NET Core proceeds by resolving a singleton IServiceScopeFactory very early in the application cycle. In a multi-tenant scenario this resolution will return the ILifetimeScope for either the first tenant requested, or for the "default" tenant, and that will be the root scope (as far as MS DI is concerned) for the application lifetime. (See the PR for further discussion.)
The classes below implement an alternate behavior: instead of resolving the DI interfaces, it builds (news-up) the initially-requested ones from the IContainer directly. With the initial IServiceScopeFactory based directly on IContainer, further scope requests will resolve correctly.
public class ContainerServiceProvider : IServiceProvider, ISupportRequiredService
{
private readonly IContainer container;
public ContainerServiceProvider(IContainer container)
{
this.container = container;
}
public object GetRequiredService(Type serviceType)
{
if (TryGetContainer(serviceType, out object containerSvc)) return containerSvc;
else return container.Resolve(serviceType);
}
public object GetService(Type serviceType)
{
if (TryGetContainer(serviceType, out object containerSvc)) return containerSvc;
else return container.ResolveOptional(serviceType);
}
bool TryGetContainer(Type serviceType, out object containerSvc)
{
if (serviceType == typeof(IServiceProvider)) { containerSvc = this; return true; }
if (serviceType == typeof(IServiceScopeFactory)) { containerSvc = new ContainerServiceScopeFactory(container); return true; }
else { containerSvc = null; return false; }
}
}
// uses IContainer, but could use copy of AutofacServiceScopeFactory
internal class ContainerServiceScopeFactory : IServiceScopeFactory
{
private IContainer container;
public ContainerServiceScopeFactory(IContainer container)
{
this.container = container;
}
public IServiceScope CreateScope()
{
return new BecauseAutofacsIsInternalServiceScope(container.BeginLifetimeScope());
}
}
// direct copy of AutofacServiceScope
internal class BecauseAutofacsIsInternalServiceScope : IServiceScope
{
private readonly ILifetimeScope _lifetimeScope;
/// <summary>
/// Initializes a new instance of the <see cref="AutofacServiceScope"/> class.
/// </summary>
/// <param name="lifetimeScope">
/// The lifetime scope from which services should be resolved for this service scope.
/// </param>
public BecauseAutofacsIsInternalServiceScope(ILifetimeScope lifetimeScope)
{
this._lifetimeScope = lifetimeScope;
this.ServiceProvider = this._lifetimeScope.Resolve<IServiceProvider>();
}
/// <summary>
/// Gets an <see cref="IServiceProvider" /> corresponding to this service scope.
/// </summary>
/// <value>
/// An <see cref="IServiceProvider" /> that can be used to resolve dependencies from the scope.
/// </value>
public IServiceProvider ServiceProvider { get; }
/// <summary>
/// Disposes of the lifetime scope and resolved disposable services.
/// </summary>
public void Dispose()
{
this._lifetimeScope.Dispose();
}
}
Fixing Identification Strategy
As for making the identification-strategy a service, I would rework your implementation like so:
public class MultitenantIdentificationStrategy : ITenantIdentificationStrategy
{
public const string DefaultTenantId = null;
private readonly IHttpContextAccessor contextaccessor;
public MultitenantTenantIdentificationStrategy(IHttpContextAccessor contextaccessor)
{
this.contextaccessor = contextaccessor;
}
public bool TryIdentifyTenant(out object tenantId)
{
var context = contextaccessor.HttpContext;
// after this is unchanged
.
.
}
.
.
}
Use in Startup.ConfigureServices
This shows the fragment of how these last few pieces are registered and fed to MS DI for ASP.NET.
. . .
builder.RegisterType<MultitenantIdentificationStrategy>().AsImplementedInterfaces(); // tenant identification
// register do Autofac DI integration
builder.Populate(services);
var underlyingcontainer = builder.Build();
ApplicationContainer = new MultitenantContainer(underlyingcontainer.Resolve<ITenantIdentificationStrategy>(), underlyingContainer);
return new ContainerServiceProvider(ApplicationContainer);
If you find this solution workable, please give a thumbs up to DI PR 10--or PR 11, if after reviewing you think that is the better/more elegant solution. Either will save having to add the "shim" code above.

How to access Request.Properties outside of Web API Controller

I'm setting a Property on Request.Properties inside a DelegatingHandler after I pluck some data out of a header on an incoming request to a Web API.
This all works fine. I can also access Request.Properties from within the controller as well as in my Action and Exception filters. However, I also need to access this data from outside of the controller (I call a business layer class from the controller). It is data I want to include in some logs in other places,
I can see HttpContext.Current from this class, and I can see the original header from here, so I guess I could pluck it out again, but since I have already done this and put it in the Properties it seems to make more sense to get it from there. However, I don't seem to have access to the Request.Properties from anywhere else.
If this isn't the right way to do this, how else would I pass around this per-request data so that it was accessible from anywhere on the stack in Web API?
I also need to access [Request.Properties] data from outside of the controller (I call a business layer class from the controller). It is data I want to include in some logs in other places... However, I don't seem to have access to the Request.Properties from anywhere else. If this isn't the right way to do this, how else would I pass around this per-request data so that it was accessible from anywhere on the stack in Web API?
You can get it from HttpContext.Current, though it is less than ideal. Keep in mind that if any other non-web applications consume the same business layer, then HttpContext.Current would be null. HttpContext.Current is only non-null when you are running in IIS, and an IIS thread is handling the execution of the request stack. If you ever plan to self-host the web api using OWIN without IIS, there will be no HttpContext.Current.
Personally, if the data really is important enough to be passed into the business layer to be logged, then I would just pass it to the business layer method:
public Task<HttpResponseMessage> SomeAction(SomeModel model) {
... other code
someBusinessLayerObject.SomeMethod(arg1, arg2, Request.Properties["myHeaderKey"]);
}
...If you need other values from Request.Properties, then you can just pass the whole dictionary to the methods that will end up using its values.
A third option if you are using an inversion of control container would be to add some kind of scoped object dependency class and put the data in there. Then constructor inject it into your business layer class:
public interface IHaveRequestData {
IDictionary<string, object> Properties { get; set; }
}
public class RequestData : IHaveRequestData {
public IDictionary<string, object> Properties { get; set; }
}
// ioc registration pseudocode
iocContainer.Register<IHaveRequestData, RequestData>(Lifetime
.WhateverYouNeedSoThatOneOfTheseGetsCreatedForEachWebRequest);
public class SomeController : ApiController {
private readonly IHaveRequestData RequestData;
public SomeController(IHaveRequestData requestData) {
RequestData = requestData;
}
public Task<HttpResponseMessage> SomeAction() {
// you may even be able to do this part in an action filter
RequestData.Properties = Request.Properties;
}
}
public class SomeBusinessLayerComponent {
private readonly IHaveRequestData RequestData;
private readonly ILog Log;
public SomeBusinessLayerComponent(IHaveRequestData requestData, ILog log) {
RequestData = requestData;
Log = log;
}
public Task SomeMethod() {
Log.Info(RequestData["myHeader"]);
}
}

Create an instance of ISession per ViewModel

here is my problem: I'm building a desktop application, with the following tools:
Caliburn
Ninject
NHibernate
All my view models and repositories are instanciated with Ninject. My repositories all need an ISession in their constructor.
I'd like to follow ayende's advice concerning the ViewModels: each ViewModel opens a new session.
Is it possible to configure Ninject to open a new session when a ViewModel is created, and use this session inside the repositories used by this view model?
I had a look to the InScope function of Ninject, as well as the ICurrentSessionContext interface in NHibernate, but I don't know how to model all of that to get what I want...
Did someone make something like that before?
Thanks in advance
Mike
I solved a similar scenario leveraging the ViewModel lifecycle: I created an ISessionAware interface (with a SetSession method) to be implemented by repositories, then I initialized the repositories through ISessionAware in the OnInitialize method of the ViewModel (which is enforced by Caliburn when the VM is managed by a ScreenConductor).
Using reflection to inspect the properties holding the repositories, I could put all the infrastructure on a BaseDataVM class.
Using a scope in the container would be more elegant, I think, but I don't know Ninject.
I have a very similar project (except I'm not using Caliburn) and have been trying to figure out how to do this as well. I did come up with one method that works well for constructor injection using Ninject's InScope() method.
I have a static class called IoC that wraps access to Ninject's kernel. Since the dependencies are all injected into the constructor, the context is only relevant when the object is being created. So it doesn't matter what is supplied for context, but a Guid feels like the safe choice. Program.OpenSession() is a static method to open a new ISession.
public static class Ioc
{
private static readonly IKernel _kernel;
static IoC()
{
_kernel = new StandardKernel();
_kernel.Load(new ContextModule());
}
private static object _context;
public static T ResolveInContext<T>(object context)
{
_context = context;
var result = _kernel.Get<T>();
_context = null;
return result;
}
private class ContextModule : NinjectModule
{
public override void Load()
{
Bind<ISession>().ToMethod(x => Program.OpenSession()).InScope(x => _context);
Bind<frmCompanyViewer>().ToSelf().InScope(x => _context);
}
}
}
Usage is:
var frm = IoC.ResolveInContext<frmCompanyViewer>(Guid.NewGuid());
The form's constructor signature is:
public frmCompanyViewer(ISession session, ICompanyRepository companyRepository)
I verified that with InScope on the bindings, the same ISession that is used to construct frmCompanyViewer is also used to construct companyRepository. If I remove InScope then two ISessions are used.
Edited to add: This would also work, see comments. This should be made thread safe for a real application. I changed the method name to ConstructInContext to clarify that the context only applies during object construction.
public static T ConstructInContext<T>()
{
_context = Guid.NewGuid();
var result = _kernel.Get<T>();
_context = null;
return result;
}
We have this with AOP, in unhaddins.
Is called "Conversation per Business Transaction".
search in google
here you have ;)
http://groups.google.com/group/unhaddins/browse_thread/thread/29eca74a83df5faf/d9fab4062d4cb4c4?lnk=gst&q=ninject#d9fab4062d4cb4c4
Well, I've found a solution thanks to the ninject group.
The solution here is to use the function InScope when I bind ISession, and browse in the IContext variable to inspect the services. If one service in the request hierarchy is assignable to the base class of my view models, I use the context as scope.
So the first time an ISession will be injected in the constructor of my ViewModel, a new scope is used. And all subsequent calls to ISession inside the constructor of the ViewModel will be resolved with the same scope. And then only one session is created for my ViewModel.
Here is the code:
Bind<ISession>().ToMethod(ctx =>
{
var session = ctx.Kernel.Get<INHibernateSessionFactoryBuilder>()
.GetSessionFactory()
.OpenSession();
session.FlushMode = FlushMode.Commit;
return session;
})
.InScope(ctx =>
{
var request = ctx.Request;
if (request.Service is IScreen)
return request;
while ((request = request.ParentRequest) != null)
if (typeof(IScreen).IsAssignableFrom(request.Service))
return request;
return new object();
});
And the constructor of the viewmodel must contains all the injected dependencies which rely on the ISession:
[Inject]
public PlayersManagementViewModel(ISession session, IPlayersRepository playersRepository)
{
}
Hope that helps