Reusing Registrations with Castle Windsor Installers? - dependency-injection

We currently have 2 web applications, one is a front end customer facing application while the other is an administrative backend application. What we noticed is that alot of registration is duplicated between the 2 applications. Example, RavenDb Setup. For example both applications have this code in the asp.net global.asax
container.Register(
Component.For<IDocumentStore>()
.UsingFactoryMethod(x =>
{
var docStore = new DocumentStore { ConnectionStringName = "RavenDB" };
docStore.Initialize();
return docStore;
}).LifestyleSingleton()
);
We refactored this code out into an installer and placed it in an assembly called CastleWindsor.RavenDbInstaller that can be referenced and reused by both applications.
public class RavenDbInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
Component.For<IDocumentStore>()
.UsingFactoryMethod(x =>
{
var docStore = new DocumentStore { ConnectionStringName = "RavenDB" };
docStore.Initialize();
return docStore;
}).LifestyleSingleton()
);
}
}
All is fine but is this the recommended approach to reusing registration logic between applications??
Also, What happens when an installer in a seperate assembly has a dependency on another class. How should this be handled. For example, What if my ravendb connectionstring should not be hardcoded and should be attached to a ApplicationConfiguration class. How do i deal with this dependency in regards to my CastleWindsor.RavenDbInstaller Assembly and the installer class it contains?
public class RavenDbInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
Component.For<IDocumentStore>()
.UsingFactoryMethod((c, y) =>
{
var connectionStringName = c.Resolve<IApplicationConfiguration>().ConnectionStringName; // <---- How do i deal with this dependency?
var docStore = new DocumentStore { ConnectionStringName = connectionStringName };
docStore.Initialize();
return docStore;
}).LifestyleSingleton()
);
}
}

if you want to use the same implemetation for IApplicationConfiguration for backend and frontend then it makes sense to place it in CastleWindsor.RavenDbInstaller Assembly. Otherwise not.
Cheers.

Personally I would leave the installer into the assembly where the components it references are. You will not resolve a component that you don't install, or resolve a component you don't use, so separating both elements doesn't really make sense to me. Both application should reference a business component that uses RavenDB and also contains install elements.
Regarding your reference to the connection string, I would recommend adding a custom resolver to your castle installation that will be able to resolve your connection string from whatever source you need: this lets you resolve the connection string in the installer without having a reference on external components. Another bonus is that if you don't register a way to resolve the connection string you don't have to handle errors, the resolution will fail explicitly with the name of the connection string.
public class ConnectionStringResolver : ISubDependencyResolver
{
public bool CanResolve(Castle.MicroKernel.Context.CreationContext context, ISubDependencyResolver contextHandlerResolver, Castle.Core.ComponentModel model, Castle.Core.DependencyModel dependency)
{
var connectionStringInformation = ConfigurationManager.ConnectionStrings[dependency.DependencyKey];
return connectionStringInformation != null
&& !string.IsNullOrEmpty(connectionStringInformation.ConnectionString)
&&
TypeDescriptor.GetConverter(dependency.TargetType)
.CanConvertFrom(typeof(string));
}
public object Resolve(Castle.MicroKernel.Context.CreationContext context, ISubDependencyResolver contextHandlerResolver, Castle.Core.ComponentModel model, Castle.Core.DependencyModel dependency)
{
return TypeDescriptor
.GetConverter(dependency.TargetType)
.ConvertFrom(ConfigurationManager.ConnectionStrings[dependency.DependencyKey].ConnectionString);
}
}
Then your installer would be:
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
Component.For<IDocumentStore>()
.UsingFactoryMethod((c, y) =>
{
var connectionStringName = container.Resolve("TheConnectionString");
var docStore = new DocumentStore { ConnectionStringName = connectionStringName };
docStore.Initialize();
return docStore;
}).LifestyleSingleton()
);
}

Related

How to instantiate a class that implements dependency injection?

I am getting the handle on .NET Core 6 and I am stuck. I am using AutoMapper and I have dependency injection set up.
My implementation:
public class LSif : ISif
{
private readonly DataContext _db;
private readonly IMemoryCache _memoryCache;
public LSif(DataContext db, IMemoryCache memoryCache)
{
_db = db;
_memoryCache = memoryCache;
}
public List<DropDown> MjernaJedinicaDD(int selected)
{
string key = "MjernaJedinicaDD" + selected;
List<DropDown> dd = new List<DropDown>();
if (!_memoryCache.TryGetValue(key, out dd))
{
var model = GetAllMjernaJedinica();
if (model != null)
{
foreach (var item in model)
{
dd.Add(
new DropDown()
{
Id = item.Id,
Name = item.Name,
Selected = selected
}
);
}
}
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromSeconds(30));
_memoryCache.Set(key, dd, cacheEntryOptions);
}
return dd;
}
}
My goal is to call that implementation method from Automapper resolver:
.ForMember(d => d.MjernaJedinicaDD, o => o.MapFrom<MjernaJedinicaDDArtikal>());
And the resolver looks like this:
public class MjernaJedinicaDDArtikal : IValueResolver<Artikal, ArtikalVM, List<DropDown>>
{
public List<DropDown> Resolve(Artikal source, ArtikalVM destination, List<DropDown> member, ResolutionContext context)
{
var services = new ServiceCollection(); // With this i shoud work
services.AddScoped<ISif, LSif>(); // but i doesn't
using ServiceProvider serviceProvider = services.BuildServiceProvider(validateScopes: true);
using (IServiceScope scope = serviceProvider.CreateScope())
{
ISif reff = scope.ServiceProvider.GetRequiredService<ISif>();
if (reff != null)
{
return reff.MjernaJedinicaDD(source.MjernaId);
}
}
return null;
// This is how I did it in .NET Framework 4.5
var lSif = new LSif();
return lSif.MjernaJedinicaDD(source.MjernaId);
}
}
Question: how to instantiate / access class that has dependency injection components (parameters) form AutoMapper custom resolver?
Aditional info:
I initiate AutoMapper using
public interface IMapFrom<T>
{
void Mapping(Profile profile) => profile.CreateMap(typeof(T), GetType());
}
and then
public class AutoMapperProfile : Profile
{
public AutoMapperProfile()
{
ApplyMappingsFromAssembly(Assembly.GetExecutingAssembly());
}
private void ApplyMappingsFromAssembly(Assembly assembly)
{
var types = assembly.GetExportedTypes()
.Where(t => t.GetInterfaces().Any(i =>
i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapFrom<>)))
.ToList();
foreach (var type in types)
{
var instance = Activator.CreateInstance(type);
var methodInfo = type.GetMethod("Mapping") ??
type.GetInterface("IMapFrom`1").GetMethod("Mapping");
methodInfo?.Invoke(instance, new object[] { this });
}
}
}
and finaly in program.cs
builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly());
When you call one of the AddAutoMapper extension methods on IServiceCollection provided by the NuGet package AutoMapper.Extensions.Microsoft.DependencyInjection during startup, this does several things including:
Adding your custom value resolver (e.g. MjernaJedinicaDDArtikal) to the dependency injection (DI) container
Configuring the Mapper to resolve dependencies from the container needed by your custom components that implement AutoMapper interfaces (such as IValueResolver)
(For additional details on what the AddAutoMapper method does, see the README for the NuGet package.)
This allows you to use constructor injection to directly supply the dependencies that your custom value resolver needs. These dependencies will be resolved from the DI container and they can also require other dependencies from the container themselves.
Your custom value resolver becomes:
public class MjernaJedinicaDDArtikal : IValueResolver<Artikal, ArtikalVM, List<DropDown>>
{
private readonly ISif _isif;
public MjernaJedinicaDDArtikal(ISif isif)
{
_isif = isif ?? throw new ArgumentNullException(nameof(isif));
}
public List<DropDown> Resolve(Artikal source, ArtikalVM destination, List<DropDown> member, ResolutionContext context)
{
return _isif.MjernaJedinicaDD(source.MjernaId);
}
}
Here is a simplified version of your AutoMapper profile that uses your custom value resolver:
public class AutoMapperProfile : Profile
{
public AutoMapperProfile()
{
CreateMap<Artikal, ArtikalVM>()
.ForMember(dest => dest.MjernaJedinicaDD, src => src.MapFrom<MjernaJedinicaDDArtikal>());
}
}
Finally, add your ISif service to the container using the LSif class as its implementation as well as any dependencies the LSif class needs.
When using the minimal hosting model for an ASP.NET Core app with .NET 6 or later, add the snippets below to Program.cs (some of the lines included with default .NET templates are included to provide context):
// TODO: add 'using' directives for namespace(s) containing ISif and LSif
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews(); // or other similar method
// Register DataContext, which is injected into LSif
// TODO: Fill in app-specific implementation
// Register IMemoryCache implementation for injection into LSif
builder.Services.AddMemoryCache(options =>
{
// TODO: fill in desired cache options
});
// Register ISif service using LSif implementation
builder.Services.AddTransient<ISif, LSif>();
builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly());
// ...
var app = builder.Build();
For other people that may want to use a similar approach but are using an earlier version of ASP.NET Core without the minimal hosting model (e.g. ASP.NET Core 3.1 or 5.0 with the Generic Host), add the custom service registrations to Startup.ConfigureServices(IServiceCollection) instead of adding them in Program.cs using builder.Services.AddXyz. For example:
public void ConfigureServices(IServiceCollection services)
{
// ...
// Register DataContext, which is injected into LSif
// TODO: Fill in app-specific implementation
// Register IMemoryCache implementation for injection into LSif
services.AddMemoryCache(options =>
{
// TODO: fill in desired cache options
});
// Register ISif service using LSif implementation
services.AddTransient<ISif, LSif>();
services.AddAutoMapper(Assembly.GetExecutingAssembly());
// ...
}
The way that AutoMapper can use the application's default DI container to resolve dependencies is that the AddAutoMapper extension method passes the IServiceProvider.GetService(Type) method as the serviceCtor argument to the Mapper constructor (source, v11.0.0).
Side note: You may also want to tweak your LSif.MjernaJedinicaDD method to avoid throwing a NullReferenceException on the line for dd.Add(...). After calling _memoryCache.TryGetValue(key, out dd), dd will be null when TryGetValue returns false because null is the default value for any C# reference type, such as a class, which List<T> is (reference).

Connect to 2 different MartenDB datastores with ASP.Net Core

When setting up a MartenDB datastore in ASP.Net Core, you normally put code like this in your Startup.cs:
services.AddMarten(o =>
{
o.Connection(configuration.GetConnectionString("MyDatabase"));
o.AutoCreateSchemaObjects = AutoCreate.All;
o.Serializer(new JsonNetSerializer { EnumStorage = EnumStorage.AsString });
});
This allows you to then inject IDocumentSession and IDocumentStore into your various classes for working with that database.
Now what do you do if you have to connect to a second database? I looked at the ISessionFactory but it is not apparent that you can change the connection string from here. Do you need to manually create and register a new DocumentStore?
To answer my own question, I wound up creating a custom DocumentStore and ISessionFactory for each database I wanted to connect to, and then injecting the custom SessionFactory.
Here's the code (only showing one instance of each class for the sake of brevity. Just replace Db1 with Db2 for the second version of each class):
The custom DocumentStore:
public class Db1Store : DocumentStore
{
public Db1Store(StoreOptions options) : base(options)
{
}
}
The custom SessionFactory:
public class Db1SessionFactory : ISessionFactory
{
private readonly Db1Store store;
public Db1SessionFactory(Db1Store store)
{
this.store = store;
}
public IQuerySession QuerySession()
{
return store.QuerySession();
}
public IDocumentSession OpenSession()
{
return store.OpenSession();
}
}
The service registration (this replaces the services.AddMarten call):
services.AddSingleton(p =>
{
var options = new StoreOptions();
options.Connection(configuration.GetConnectionString("DB1"));
options.AutoCreateSchemaObjects = AutoCreate.All;
options.Serializer(new JsonNetSerializer { EnumStorage = EnumStorage.AsString });
return new Db1Store(options);
});
services.AddSingleton<Db1SessionFactory>();
Then you inject the Db1SessionFactory instance into your class, and run a query like this:
var result = await db1SessionFactory.QuerySession().Query<MyAwesomeTable>().ToListAsync();
Downsides:
I would prefer to inject the QuerySession or DocumentSession, but I can't see a way to do that without moving to Autofac or a similar DI Container that supports named instances.
I am not sure what downsides there will be creating these QuerySession/DocumentSessions in this manner. It may be a bad tradeoff.

UWP Template 10 and Service Dendency Injection (MVVM) not WPF

I have spent over two weeks searching google, bing, stack overflow, and msdn docs trying to figure out how to do a proper dependency injection for a mobile app that I am developing. To be clear, I do DI every day in web apps. I do not need a crash course on what, who, and why DI is important. I know it is, and am always embracing it.
What I need to understand is how this works in a mobile app world, and in particular a UWP Template 10 Mobile app.
From my past, in a .net/Asp app I can "RegisterType(new XYZ).Singleton() blah" {please forgive syntax; just an example} in App_Start.ConfigureServices. This works almost identical in .netcore, granted some syntactic changes.
My problem is now I am trying to provide my api is going to an UWP app that needs to digest my IXYZ service. By no means do I think that they should "new" up an instance every time. There has to be a way to inject this into a container on the UWP side; and I feel I am missing something very simple in the process.
Here is the code I have:
App.xaml.cs
public override async Task OnStartAsync(StartKind startKind, IActivatedEventArgs args)
{
// TODO: add your long-running task here
//if (args.Kind == ActivationKind.LockScreen)
//{
//}
RegisterServices();
await NavigationService.NavigateAsync(typeof(Views.SearchCompanyPage));
}
public static IServiceProvider Container { get; private set; }
private static void RegisterServices()
{
var services = new ServiceCollection();
services.AddSingleton<IXYZ, XYZ>();
Container = services.BuildServiceProvider();
}
MainPage.xaml.cs:
public MainPage()
{
InitializeComponent();
NavigationCacheMode = NavigationCacheMode.Enabled;
}
MainPageViewModel:
public class MainPageViewModel : ViewModelBase
{
private readonly IXYZ _xyz;
public MainPageViewModel(IXYZ xyz)
{
//Stuff
_xyz= xyz;
}
}
I now get the error:
XAML MainPage...ViewModel type cannot be constructed. In order to be constructed in XAML, a type cannot be abstract, interface nested generic or a struct, and must have a public default constructor.
I am willing to use any brand of IoC Container, but what I need is an example of how to properly use DI for services in a UWP app. 99.9% of questions about DI is about Views (i.e. Prism?) not just a simple DI for a service (i.e. DataRepo; aka API/DataService).
Again, I feel I am missing something obvious and need a nudge in the right direction. Can somebody show me an example project, basic code, or a base flogging on how I should not be a programmer...please don't do that (I don't know if my ego could take it).
You can try to Microsoft.Hosting.Extensions just like ASP.NET, there's an implementation on Xamarin.Forms by James Montemagno, as well it can be used in UWP I have tried and it works perfectly. You have to change some parts in order to get it working.
In OnLaunched Method add Startup.Init();
public static class Startup
{
public static IServiceProvider ServiceProvider { get; set; }
public static void Init()
{
StorageFolder LocalFolder = ApplicationData.Current.LocalFolder;
var configFile = ExtractResource("Sales.Client.appsettings.json", LocalFolder.Path);
var host = new HostBuilder()
.ConfigureHostConfiguration(c =>
{
// Tell the host configuration where to file the file (this is required for Xamarin apps)
c.AddCommandLine(new string[] { $"ContentRoot={LocalFolder.Path}" });
//read in the configuration file!
c.AddJsonFile(configFile);
})
.ConfigureServices((c, x) =>
{
// Configure our local services and access the host configuration
ConfigureServices(c, x);
}).
ConfigureLogging(l => l.AddConsole(o =>
{
//setup a console logger and disable colors since they don't have any colors in VS
o.DisableColors = true;
}))
.Build();
//Save our service provider so we can use it later.
ServiceProvider = host.Services;
}
static void ConfigureServices(HostBuilderContext ctx, IServiceCollection services)
{
//ViewModels
services.AddTransient<HomeViewModel>();
services.AddTransient<MainPageViewModel>();
}
static string ExtractResource(string filename, string location)
{
var a = Assembly.GetExecutingAssembly();
using (var resFilestream = a.GetManifestResourceStream(filename))
{
if (resFilestream != null)
{
var full = Path.Combine(location, filename);
using (var stream = File.Create(full))
{
resFilestream.CopyTo(stream);
}
}
}
return Path.Combine(location, filename);
}
}
Injecting a ViewModel is possible as well which is pretty nice.
With help from #mvermef and the SO question Dependency Injection using Template 10 I found a solutions. This turned out to be a rabbit hole where at every turn I ran into an issue.
The first problem was just getting Dependency Injection to work. Once I was able to get that figured out from the sources above I was able to start injecting my services into ViewModels and setting them to the DataContext in the code behind.
Then I ran into an injection issue problem with injecting my IXYZ services into the ViewModels of UserControls.
Pages and their ViewModels worked great but I had issues with the DataContext of the UserControl not being injected with UserControl's ViewModel. They were instead getting injected by the Page's ViewModel that held it.
The final solution turned out to be making sure that the UserControl had the DataContext being set in XAML not the code behind, as we did with the Pages, and then creating a DependencyProperty in the code behind.
To show the basic solution read below.
To make it work I started with:
APP.XAML.CS
public override async Task OnStartAsync(StartKind startKind, IActivatedEventArgs args)
{
// long-running startup tasks go here
RegisterServices();
await Task.CompletedTask;
}
private static void RegisterServices()
{
var services = new ServiceCollection();
services.AddSingleton<IRepository, Repository>();
services.AddSingleton<IBinderService, BinderServices>();
**//ViewModels**
**////User Controls**
services.AddSingleton<AddressesControlViewModel, AddressesControlViewModel>();
services.AddSingleton<CompanyControlViewModel, CompanyControlViewModel>();
**//ViewModels**
**////Pages**
services.AddSingleton<CallListPageViewModel, CallListPageViewModel>();
services.AddSingleton<CallListResultPageViewModel, CallListResultPageViewModel>();
etc....
Container = services.BuildServiceProvider();
}
public override INavigable ResolveForPage(Page page, NavigationService navigationService)
{
**//INJECT THE VIEWMODEL FOR EACH PAGE**
**//ONLY THE PAGE NOT USERCONTROL**
if (page is CallListPage)
{
return Container.GetService<CallListPageViewModel>();
}
if (page is CallListResultPage)
{
return Container.GetService<CallListResultPageViewModel>();
}
etc...
return base.ResolveForPage(page, navigationService);
}
In the code behind for the Page
CALLLISTPAGE.XAML.CS
public CallListPage()
{
InitializeComponent();
}
CallListPageViewModel _viewModel;
public CallListPageViewModel ViewModel
{
get { return _viewModel ?? (_viewModel = (CallListPageViewModel)DataContext); }
}
In your XAML add your UserControl
CALLLISTPAGE.XAML
<binder:CompanyControl Company="{x:Bind ViewModel.SelectedCompany, Mode=TwoWay}"/>
In your UserControl make sure to add the DataContext to the XAML NOT the code behind like we did with the pages.
COMPANYCONTROL.XAML
<UserControl.DataContext>
<viewModels:CompanyControlViewModel x:Name="ViewModel" />
</UserControl.DataContext>
In the UserControl Code Behind add a Dependency Property
COMPANYCONTROL.XAML.CS
public static readonly DependencyProperty CompanyProperty = DependencyProperty.Register(
"Company", typeof(Company), typeof(CompanyControl), new PropertyMetadata(default(Company), SetCompany));
public CompanyControl()
{
InitializeComponent();
}
public Company Company
{
get => (Company) GetValue(CompanyProperty);
set => SetValue(CompanyProperty, value);
}
private static void SetCompany(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = d as CompanyControl;
var viewModel = control?.ViewModel;
if (viewModel != null)
viewModel.Company = (Company) e.NewValue;
}
In the end I am not sure if this is an elegant solution but it works.

Cannot get a working Unity Session Lifetime Manager, ASP.NET MVC5

I've read and Googled everything on this, but can't seem to get it to work. I created a custom LifetimeManager for Unity in my MVC5 application based on these posts:
MVC3 Unity Framework and Per Session Lifetime Manager
This may be the issue I am experiencing
Here is my SessionLifetimeManager
public class SessionLifetimeManager : LifetimeManager
{
private string key = Guid.NewGuid().ToString();
public override object GetValue()
{
return HttpContext.Current.Session[key];
}
public override void RemoveValue()
{
HttpContext.Current.Session.Remove(key);
}
public override void SetValue(object newValue)
{
HttpContext.Current.Session[key] = newValue;
}
}
I only have a few types I'm playing with, here is the relevant registrations in UnityConfig.cs:
container.RegisterType<IEpiSession, EpiSession>(new SessionLifetimeManager(),
new InjectionConstructor(config.AppServerURI, config.PathToSysConfig));
container.RegisterType<IReportRepository, EpicorReportRepository>(new TransientLifetimeManager());
DependencyResolver.SetResolver(new UnityDependencyResolver(container));
Note that the EpicorReportRepository has a dependency on IEpiSession via constructor injection.
public class EpicorReportRepository : IReportRepository
{
private IEpiSession session;
// DI constructor
public EpicorReportRepository(IEpiSession session) {
this.session = session;
}
// ...
}
My Problem: After the first user / session connects to the application, every new user / session after that seems to still be using the EpiSession object and credentials that the first user had create/injected for him. This seems to be a common pattern used on the interwebs, so I'm wondering what I am missing.
How did you test that IEpiSession is the same in different Sessions?
Try to open you application from different browsers. If you open several tabs in the same browser then the same session is used.
I checked your code and it works for me.
There is the only one difference in SetResolver():
DependencyResolver.SetResolver(
type => container.Resolve(type),
types => container.ResolveAll(types));
The full registration code is the following:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
...
var container = new UnityContainer();
container.RegisterType<IEpiSession, EpiSession>(
new SessionLifetimeManager(),
new InjectionConstructor("config.AppServerURI", "config.PathToSysConfig"));
container.RegisterType<IReportRepository, EpicorReportRepository>(new TransientLifetimeManager());
DependencyResolver.SetResolver(
type => container.Resolve(type),
types => container.ResolveAll(types));
}
}

Manage multiple ravendb document stores through castle windsor in an MVC app?

I twist myself around a workable solution to use several databases in RavenDB for an ASP.Net MVC app using Castle Windsor for the wiring.
This is the current installer
public class RavenInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
Component.For<IDocumentStore>().Instance(CreateDocumentStore()).LifeStyle.Singleton,
Component.For<IDocumentSession>().UsingFactoryMethod(GetDocumentSesssion).LifeStyle.PerWebRequest
);
}
static IDocumentStore CreateDocumentStore()
{
var store = new DocumentStore { ConnectionStringName = "RavenDb_CS9" };
store.Initialize();
IndexCreation.CreateIndexes(typeof(Users).Assembly, store);
return store;
}
static IDocumentSession GetDocumentSesssion(IKernel kernel)
{
var store = kernel.Resolve<IDocumentStore>();
return store.OpenSession();
}
}
The above works perfect but only for one Database.
I can't find the proper thinking how to handle another database. The whole chain starts with a domain service asking for an IDocumentSession. Then the flow is as specified in the above installer. But where/how do I ask for a "SessionToDb1" or a "SessionToDb2"?
The important is of course what connection string to use (where the DB property is specified) but also what indexes to create in respective DB / DocumentStore.
Did anyone accomplish this using Windsor? Am I thinking/attacking it wrong here?
Thanks!
Because you have:
Component.For<IDocumentSession>()
.UsingFactoryMethod(GetDocumentSesssion)
.LifeStyle.PerWebRequest
Your GetDocumentSession method is going to be called any time you inject an IDocumentSession. This is good.
When working with multiple databases, you need to pass the database name as a parameter to OpenSession. So, you need some way to resolve which database you would like to connect to based on the current web request.
You need to modify the GetDocumentSession method to implement whatever custom logic you are going to use. For example, you may want to look at a cookie, asp.net session item, current thread principal, or some other criteria. The decision is custom to your application, all that matters is somehow you open the session with the correct database name.
I've run into this problem before with nhibernate.
I found the best solution is to create a SessionManager class which wraps the Creation of the document store and the Session..
So I.E.
public interface ISessionManager
{
void BuildDocumentStore();
IDocumentSession OpenSession();
}
public interface ISiteSessionManager : ISessionManager
{
}
public class SiteSessionManager : ISiteSessionManager
{
IDocumentStore _documentStore;
public SiteSessionManager()
{
BuildDocumentStore();
}
public void BuildDocumentStore()
{
_documentStore = new DocumentStore
{
Url = "http://localhost:88",
DefaultDatabase = "test"
};
_documentStore.Initialize();
IndexCreation.CreateIndexes(typeof(SiteSessionManager).Assembly, _documentStore);
}
public IDocumentSession OpenSession()
{
return _documentStore.OpenSession();
}
}
// And then!.
Container.Register(Component.For<ISiteSessionManager>().Instance(new SiteSessionManager()).LifestyleSingleton());
// And then!.
public class FindUsers
{
readonly ISiteSessionManager _siteSessionManager;
public FindUsers(ISiteSessionManager siteSessionManager)
{
_siteSessionManager = siteSessionManager;
}
public IList<User> GetUsers()
{
using (var session = _siteSessionManager.OpenSession())
{
// do your query
return null;
}
}
}
Rinse and repeat for multiple databases.!

Resources