public void GetOrAddNamedOptionForCurrentTenantOnly(string name)
    {
        var ti = new TenantInfo("test-id-123", null, null, null, null);
        var tc = new MultiTenantContext();

        tc.TenantInfo = ti;
        var tca   = new TestMultiTenantContextAccessor(tc);
        var cache = new MultiTenantOptionsCache <InMemoryStoreOptions>(tca);

        var options  = new InMemoryStoreOptions();
        var options2 = new InMemoryStoreOptions();

        // Add new options.
        var result = cache.GetOrAdd(name, () => options);

        Assert.Same(options, result);

        // Get the existing options if exists.
        result = cache.GetOrAdd(name, () => options2);
        Assert.NotSame(options2, result);

        // Confirm different tenant on same object is an add (ie it didn't exist there).
        ti.Id  = "diff_id";
        result = cache.GetOrAdd(name, () => options2);
        Assert.Same(options2, result);
    }
        public static async Task Main(string[] args)
        {
            var host = CreateHostBuilder(args).Build();

            // Set up the databases for the sample if needed.
            var env = host.Services.GetService <IWebHostEnvironment>();

            if (env.EnvironmentName == "Development")
            {
                var config  = host.Services.GetRequiredService <IConfiguration>();
                var options = new InMemoryStoreOptions();
                config.GetSection("Finbuckle:MultiTenant:InMemoryStore").Bind(options);
                foreach (var tenant in options.TenantConfigurations.Where(tc => tc.ConnectionString != null))
                {
                    using (var db = new ApplicationDbContext(new TenantInfo(null, null, null, tenant.ConnectionString, null)))
                    {
                        await db.Database.MigrateAsync();
                    }
                }

                using (var db = new ApplicationDbContext(new TenantInfo(null, null, null, options.DefaultConnectionString, null)))
                {
                    await db.Database.MigrateAsync();
                }
            }

            await host.RunAsync();
        }
    public void AddNamedOptionsForCurrentTenantOnlyOnAdd(string name)
    {
        var ti = new TenantInfo("test-id-123", null, null, null, null);
        var tc = new MultiTenantContext();

        tc.TenantInfo = ti;
        var tca   = new TestMultiTenantContextAccessor(tc);
        var cache = new MultiTenantOptionsCache <InMemoryStoreOptions>(tca);

        var options = new InMemoryStoreOptions();

        // Add new options.
        var result = cache.TryAdd(name, options);

        Assert.True(result);

        // Fail adding options under same name.
        result = cache.TryAdd(name, options);
        Assert.False(result);

        // Change the tenant id and confirm options can be added again.
        ti.Id  = "diff_id";
        result = cache.TryAdd(name, options);
        Assert.True(result);
    }
    public void HandleNullMultiTenantContextOnGetOrAdd()
    {
        var tca   = new TestMultiTenantContextAccessor(null);
        var cache = new MultiTenantOptionsCache <InMemoryStoreOptions>(tca);

        var options = new InMemoryStoreOptions();

        // Add new options, ensure no exception caused by null MultiTenantContext.
        var result = cache.GetOrAdd("", () => options);

        Assert.NotNull(result);
    }
        /// <summary>
        /// Creates an InMemoryStore from configured InMemoryMultiTenantStoreOptions.
        /// </summary>
        private IMultiTenantStore InMemoryStoreFactory(Action <InMemoryStoreOptions> config, bool ignoreCase, ILogger <MultiTenantStoreWrapper <InMemoryStore> > logger)
        {
            if (config == null)
            {
                throw new ArgumentNullException(nameof(config));
            }

            var options = new InMemoryStoreOptions();

            config(options);
            var store = new MultiTenantStoreWrapper <InMemoryStore>(new InMemoryStore(ignoreCase), logger);

            try
            {
                foreach (var tenantConfig in options.TenantConfigurations ?? new InMemoryStoreOptions.TenantConfiguration[0])
                {
                    if (string.IsNullOrWhiteSpace(tenantConfig.Id) ||
                        string.IsNullOrWhiteSpace(tenantConfig.Identifier))
                    {
                        throw new MultiTenantException("Tenant Id and Identifer cannot be null or whitespace.");
                    }

                    var tenantInfo = new TenantInfo(tenantConfig.Id,
                                                    tenantConfig.Identifier,
                                                    tenantConfig.Name,
                                                    tenantConfig.ConnectionString ?? options.DefaultConnectionString,
                                                    null);

                    foreach (var item in tenantConfig.Items ?? new Dictionary <string, string>())
                    {
                        tenantInfo.Items.Add(item.Key, item.Value);
                    }

                    if (!store.TryAddAsync(tenantInfo).Result)
                    {
                        throw new MultiTenantException($"Unable to add {tenantInfo.Identifier} is already configured.");
                    }
                }
            }
            catch (Exception e)
            {
                throw new MultiTenantException
                          ("Unable to add tenant to store.", e);
            }

            return(store);
        }
    public void RemoveNamedOptionsForCurrentTenantOnly(string name)
    {
        var ti = new TenantInfo("test-id-123", null, null, null, null);
        var tc = new MultiTenantContext();

        tc.TenantInfo = ti;
        var tca   = new TestMultiTenantContextAccessor(tc);
        var cache = new MultiTenantOptionsCache <InMemoryStoreOptions>(tca);

        var options = new InMemoryStoreOptions();

        // Add new options.
        var result = cache.TryAdd(name, options);

        Assert.True(result);

        // Add under a different tenant.
        ti.Id  = "diff_id";
        result = cache.TryAdd(name, options);
        Assert.True(result);
        result = cache.TryAdd("diffname", options);
        Assert.True(result);

        // Remove named options for current tenant.
        result = cache.TryRemove(name);
        Assert.True(result);
        var tenantCache = (ConcurrentDictionary <string, IOptionsMonitorCache <InMemoryStoreOptions> >)cache.GetType().
                          GetField("map", BindingFlags.NonPublic | BindingFlags.Instance).
                          GetValue(cache);

        dynamic tenantInternalCache = tenantCache[ti.Id].GetType().GetField("_cache", BindingFlags.NonPublic | BindingFlags.Instance)
                                      .GetValue(tenantCache[ti.Id]);

        // Assert named options removed and other options on tenant left as-is.
        Assert.False(tenantInternalCache.Keys.Contains(name ?? ""));
        Assert.True(tenantInternalCache.Keys.Contains("diffname"));

        // Assert other tenant not affected.
        ti.Id = "test-id-123";
        tenantInternalCache = tenantCache[ti.Id].GetType().GetField("_cache", BindingFlags.NonPublic | BindingFlags.Instance)
                              .GetValue(tenantCache[ti.Id]);
        Assert.True(tenantInternalCache.ContainsKey(name ?? ""));
    }
    public void ClearOptionsForCurrentTenantOnly()
    {
        var ti = new TenantInfo("test-id-123", null, null, null, null);
        var tc = new MultiTenantContext();

        tc.TenantInfo = ti;
        var tca   = new TestMultiTenantContextAccessor(tc);
        var cache = new MultiTenantOptionsCache <InMemoryStoreOptions>(tca);

        var options = new InMemoryStoreOptions();

        // Add new options.
        var result = cache.TryAdd("", options);

        Assert.True(result);

        // Add under a different tenant.
        ti.Id  = "diff_id";
        result = cache.TryAdd("", options);
        Assert.True(result);

        // Clear options on first tenant.
        ti.Id = "test-id-123";
        cache.Clear();

        // Assert options cleared on this tenant.
        var tenantCache = (ConcurrentDictionary <string, IOptionsMonitorCache <InMemoryStoreOptions> >)cache.GetType().
                          GetField("map", BindingFlags.NonPublic | BindingFlags.Instance).
                          GetValue(cache);

        dynamic tenantInternalCache = tenantCache[ti.Id].GetType().GetField("_cache", BindingFlags.NonPublic | BindingFlags.Instance)
                                      .GetValue(tenantCache[ti.Id]);

        Assert.True(tenantInternalCache.IsEmpty);

        // Assert options still exist on other tenant.
        ti.Id = "diff_id";
        tenantInternalCache = tenantCache[ti.Id].GetType().GetField("_cache", BindingFlags.NonPublic | BindingFlags.Instance)
                              .GetValue(tenantCache[ti.Id]);
        Assert.False(tenantInternalCache.IsEmpty);
    }