Giter Site home page Giter Site logo

finbuckle / finbuckle.multitenant Goto Github PK

View Code? Open in Web Editor NEW
1.2K 45.0 247.0 2.64 MB

Finbuckle.MultiTenant is an open-source multitenancy middleware library for .NET. It enables tenant resolution, per-tenant app behavior, and per-tenant data isolation.

Home Page: https://www.finbuckle.com/multitenant

License: Apache License 2.0

C# 99.60% Shell 0.40%
aspnetcore multitenant efcore finbuckle dotnet dotnetcore middleware entity-framework-core aspnetcore-identity

finbuckle.multitenant's Introduction

Finbuckle Logo Finbuckle.MultiTenant 7.0.1

About Finbuckle.MultiTenant

Finbuckle.MultiTenant is an open-source multitenancy middleware library for .NET. It enables tenant resolution, per-tenant app behavior, and per-tenant data isolation. See https://www.finbuckle.com/multitenant for more details and documentation.

Current publish feed release:
Finbuckle.MultiTenant NuGet.org badge

Table of Contents

  1. What's New in v7.0.1
  2. Quick Start
  3. Documentation
  4. Sample Projects
  5. Build and Test Status
  6. License
  7. .NET Foundation
  8. Code of Conduct
  9. Community
  10. Building from Source
  11. Running Unit Tests

What's New in v7.0.1

This section only lists release update details specific to v7.0.1. See the changelog file for all release update details.

Bug Fixes

  • only throw exception in EnforceMultiTenant for null tenant if there are entity changes. (#819) (ca9e9fd)

Quick Start

Finbuckle.MultiTenant is designed to be easy to use and follows standard .NET conventions as much as possible. This introduction assumes a standard ASP.NET Core use case, but any application using .NET dependency injection can work with the library.

Installation

First, install the Finbuckle.MultiTenant.AspNetCore NuGet package:

.NET Core CLI

$ dotnet add package Finbuckle.MultiTenant.AspNetCore

Basic Configuration

Next, in the app's service configuration call AddMultiTenant<TTenantInfo> and its various builder methods and in the middleware configuration call UseMultiTenant():

builder.Services.AddMultiTenant<TenantInfo>()
    .WithHostStrategy()
    .WithConfigurationStore();

// other app code...

app.UseMultiTenant();

// other app code...

app.Run();

That's all that is needed to get going. Let's breakdown each line:

builder.Services.AddMultiTenant<TenantInfo>()

This line registers the base services and designates TenantInfo as the class that will hold tenant information at runtime.

The type parameter for AddMultiTenant<TTenantInfo> must be an implementation of ITenantInfo which holds basic information about the tenant such as its name and an identifier. TenantInfo is provided as a basic implementation, but a custom implementation can be used if more properties are needed.

See Core Concepts for more information on ITenantInfo.

.WithHostStrategy()

The line tells the app that our "strategy" to determine the request tenant will be to look at the request host, which defaults to the extracting the subdomain as a tenant identifier.

See MultiTenant Strategies for more information.

.WithConfigurationStore()

This line tells the app that information for all tenants are in the appsettings.json file used for app configuration. If a tenant in the store has the identifier found by the strategy, the tenant will be successfully resolved for the current request.

See MultiTenant Stores for more information.

Finbuckle.MultiTenant comes with a collection of strategies and store types that can be mixed and matched in various ways.

app.UseMultiTenant()

This line configures the middleware which resolves the tenant using the registered strategies, stores, and other settings. Be sure to call it before other middleware which will use per-tenant functionality, e.g. UseAuthentication().

Basic Usage

With the services and middleware configured, access information for the current tenant from the TenantInfo property on the MultiTenantContext<T> object accessed from the GetMultiTenantContext<T> extension method:

var tenantInfo = HttpContext.GetMultiTenantContext<TenantInfo>().TenantInfo;

if(tenantInfo != null)
{
    var tenantId = tenantInfo.Id;
    var identifier = tenantInfo.Identifier;
    var name = tenantInfo.Name;
}

The type of the TenantInfo property depends on the type passed when calling AddMultiTenant<T> during configuration. If the current tenant could not be determined then TenantInfo will be null.

The ITenantInfo instance and/or the typed instance are also available directly through dependency injection.

See Configuration and Usage for more information.

Documentation

The library builds on this basic functionality to provide a variety of higher level features. See the documentation for more details:

Sample Projects

A variety of sample projects are available in the repository. Check older tagged release commits for samples from prior .NET versions.

Build and Test Status

Build and Test Status

License

This project uses the Apache 2.0 license. See LICENSE file for license information.

.NET Foundation

This project is supported by the .NET Foundation.

Code of Conduct

This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community. For more information see the .NET Foundation Code of Conduct or the CONTRIBUTING.md file.

Community

Check out the GitHub repository to ask a question, make a request, or peruse the code!

Building from Source

From the command line clone the git repository, cd into the new directory, and compile with dotnet build.

$ git clone https://github.com/Finbuckle/Finbuckle.MultiTenant.git
$ cd Finbuckle.MultiTenant
Cloning into 'Finbuckle.MultiTenant'...
<output omitted>
$ cd Finbuckle.MultiTenant
$ dotnet build

Running Unit Tests

Run the unit tests from the command line with dotnet test from the solution directory.

$ dotnet test

finbuckle.multitenant's People

Contributors

andrewtriestocode avatar blittable avatar bobbyangers avatar brockallen avatar cacpgomes avatar danjus10 avatar dependabot[bot] avatar eloekset avatar fossbrandon avatar gordonblahut avatar harisbotic avatar kakone avatar lahma avatar lamarlugli avatar lionelvallet avatar mesfinmo avatar michaelhachen avatar mphill avatar mseada94 avatar natelaff avatar nbarbettini avatar onurkanbakirci avatar paulczy avatar rchamorro avatar rikbosch avatar romanky avatar semantic-release-bot avatar sharepointradi avatar thatgoofydev avatar valks avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

finbuckle.multitenant's Issues

Add support for IOptions<T> and IOptionsSnapshot<T> and Per Tenant Options

The current implementation of WithPerTenantOptionsConfig relies on a custom implementation of IOptionsCache, however OptionsManager (which IOptions and IOptionsSnapshot resolve to) uses an internal non DI version of IOptionsCache.

Approach 1 : Get OptionsManager to use DI for IOptionsCache. I have an issue opened with aspnetcore about this here.

Approach 2: Implement IOptions and IOptionsSnapshot, i.e. OptionsManager, myself so that it uses DI for IOptionsCache. Would have to then be registered instead of the default implementation--maybe just for T which have registered using WithPerTenantOptionsConfig.

Add support for ASP.NET Core remote authentication

While we can customize remote (OAuth based and OpenId Connect) authentication related options, this is not compatible with some multitenant strategies due to callback URLs which might prevent tenant resolution.

Want to build in support for this somehow.

Repository adjustment for v1.1.0 release.

Hello, apologies to anyone with a fork, but in order to clean up the repo I decided to bump the master branch back a commit before applying the final release merge. I will try to avoid this as much as possible going forward.

Thanks.

Using ASP.Net Core, the sample didn't work, with SigninManager

I am using the IdentityCore like following:

services.AddIdentityCore<ApplicationUser>()
                .AddUserStore()
                .AddSignInManager()
                .AddDefaultTokenProviders();

When I use the following code with it:

           services
                .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme);

            services.AddSingleton<IMultiTenantStore>((serviceProvider) =>
            {
                var cfg = serviceProvider.GetService<List<TenantConfiguration>>();
                return new TenantResolver(cfg.ToArray());
            });
            services.AddMultiTenant()
                .WithHostStrategy()
                .AddPerTenantCookieAuthentication();



public static MultiTenantBuilder AddPerTenantCookieAuthentication(this MultiTenantBuilder builder)
        {
            return builder.WithPerTenantOptions<CookieAuthenticationOptions>((options, tenantContext) =>
                {
                    // Set a unique cookie name for this tenant.         
                    options.Cookie.Name = tenantContext.Id + "-cookie";
                });
        }

I get the following error: InvalidOperationException: No sign-in authentication handler is registered for the scheme 'Identity.Application'. The registered sign-in schemes are: cookies. Did you forget to call AddAuthentication().AddCookies("Identity.Application",...)?


Microsoft.AspNetCore.Authentication.AuthenticationService.SignInAsync(HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties)
Microsoft.AspNetCore.Identity.SignInManager<TUser>.SignInAsync(TUser user, AuthenticationProperties authenticationProperties, string authenticationMethod)
Profile.Controllers.AccountController.Registreer(RegisterViewModel model, string returnUrl) in AccountController.cs

                        await signInManager.SignInAsync(user, isPersistent: false);

If I change the scheme to this:

services
                .AddAuthentication("Identity.Application")
                .AddCookie("Identity.Application");

I can log in. Am I configuring something wrong? I would rather not hard code this value, to this one value I can't change.

Support custom route parameter name when using WithRouteStrategy

RouteMultiTenantStrategy supports a custom route parameter name via its contructor, but WithRouteStrategy does not use it.

Would like to add an optional string parameter routeParameter to WithRouteStrategy which defaults to null and is passed to the RouteMultiTenantStrategy constructor.

Null reference error on Add-Migration using MultiTenantIdentityDbContext

System.NullReferenceException: Object reference not set to an instance of an object.
   at Finbuckle.MultiTenant.EntityFrameworkCore.Shared.SetupModel(ModelBuilder modelBuilder, TenantContext tenantContext) in C:\...\Finbuckle.MultiTenant\src\Finbuckle.MultiTenant.EntityFrameworkCore\Shared.cs:line 93
   at Finbuckle.MultiTenant.EntityFrameworkCore.MultiTenantIdentityDbContext`1.OnModelCreating(ModelBuilder builder) in C:\...\Finbuckle.MultiTenant\src\Finbuckle.MultiTenant.EntityFrameworkCore\MultiTenantIdentityDbContext.cs:line 74

Just looks like there needs to be some null-conditional checks added to allow migrations to apply happily.

var rightExp = Expression.Constant(tenantContext.Id, typeof(string));

should be

var rightExp = Expression.Constant(tenantContext?.Id, typeof(string));

I have a branch going I can submit a PR for (just need contributor permissions).

Add logging support.

Need to add logging throughout the tenant resolution and Entity Framework flows. This will use standard ASP.NET Core logging.

Refactor route strategy support

Currently using the route strategy requires special logic in the middleware and a special version of UseMultiTenant which takes an Action<IRouteBuilder>.

A better approach would be to move this logic into the RouteMultiTenantStrategy class and to pass the Action<IRouteBuilder> into WithRouteStrategy method. Would require updates to the strategy, builder, and unit tests.

Question: default fallback tenant identifier

Which is the best way to implement a default fallback identifier with host subdomain strategy?

Suppose I registered *.domain.com in your dns. I have two tenants; tenant 1 and tenant2. I would like to redirect the user to the www.domain.com url if the tenant resolver does not find any tenant registered.

Sorry for the english. Thanks!

Hostname strategy

Hi

I am struggling to get the hostname strategy working. I'm using the EFCore InMemory store, which I populate with "TenantInfo" items. I've assumed the hostname should be set against the "Identifier" property so my setup looks like the following:

        public void ConfigureServices(IServiceCollection services)
        {
            // Register options
            services.AddOptions();

            services.AddMediatR(typeof(RetrieveTenants.Handler).Assembly);

            #region Tenant Db Configuration

            // Tenant database connection
            services.AddDbContext<ConfiguratorDbContext>(options =>
                options.UseSqlServer(Configuration["TenantDbConnection:ConnectionString"]));

            #endregion

            services.AddLogging();
            services.AddMvc()
                .SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
                .AddFluentValidation(cfg =>
                {
                    cfg.RegisterValidatorsFromAssemblyContaining(typeof(AbstractValidator<>));
                });

            #region Multitenancy configuration

            services.AddMultiTenant()
                .WithEFCoreStore<StoreDbContext, AppTenantInfo>()
                .WithHostStrategy();

            #endregion

            #region Application Db Configuration

            // Register the db context, but do not specify a provider/connection string since
            // these vary by tenant.
            services.AddDbContext<WintrixDbContext>();

            #endregion
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseDatabaseErrorPage();
            }
            else
            {
                app.UseHsts();
            }

            app.UseStaticFiles();
            app.UseMultiTenant();
            app.UseMvc();

            SetupStore(app.ApplicationServices);
        }

        private void SetupStore(IServiceProvider sp)
        {
            var scopeServices = sp.CreateScope().ServiceProvider;
            var store = scopeServices.GetRequiredService<IMultiTenantStore>();
            var mediator = scopeServices.GetRequiredService<IMediator>();

            var tenants = mediator
                .Send(new RetrieveTenants.Query
                {
                    Environment = Configuration["TenantDbConnection:Environment"]
                })
                .GetAwaiter()
                .GetResult()
                .Tenants;

            foreach (var tenant in tenants)
            {
                store.TryAddAsync(new TenantInfo(tenant.Id, tenant.Hostname, tenant.Name, tenant.ConnectionString, null)).Wait();
            }
        }

Currently I seem to get no tenantInfo when I call:

HttpContext.GetMultiTenantContext()?.TenantInfo;

My setup is meant to use our tenant database to get the tenant configuration data including connection string and hostnames. I then load this in to a TenantInfo objects with the hostname as the Identifier and insert this in to an EFCore store.

That part seems to work ok becuase I see all the tenants if I query the StoreDbContext directly.

I've added bindings in IIS for each host name and added entries to my host file.

Each tenant has a separate database which should then be resolved by the tenant connection string in the EFCoreStore, but as I said above, in my controller the tenantInfo is currently null?

Any help would be much appreciated

I guess one thing I thought about, was in your hostname sample you only replaced the subdomain, in my case the whole host name is different.

i.e.

www.something.com
another.domain.net

Andy

Support case sensitive InMemoryTenantStore when using WithInMemoryStore

Currently WithInMemoryStore adds InMemoryMultiTenantStore to the services collection using the default case insensitive option.

Would like to add an optional bool parameter ignoreCase to WithInMemoryStore, defaulting to true, which is passed to the InMemoryMultiTenantStore constructor.

Same tenant always for shared datasource regardless of current tenant

Discovered a bug related to the default caching behavior of EFCore. The global query filter is configured only for the first tenant to initialize the context, after which all contexts filter for the tenant id or that first tenant. This can be fixed by implementing a custom IModelCacheKeyFactory which accounts for the tenant.

"Global" login page possible?

Hi

I was wondering whether it would be possible to have a generic / global login page for all users, and redirect to the correct tenant for that user after login. This would preclude users having to know the tenant name.

In other words, all users login at example.com/Account/Login.
On successful login, redirect to example.com/{tenant-associated-with-user}/Home
?

This would of course require a shared identity DB, and user names would have to be unique across all tenants, but it would be consistent with most multi-tenant apps that I've seen, and be less confusing to new users.

The alternative of having a landing page listing all tenants is suboptimal (to me), as it advertises who all your customers are.

I'm happy to tinker away if you think it would be possible, but perhaps you can see some structural reasons why it wouldn't work.

Thanks
Wojt.

Remote authentication problem

Request handlers aren't being detected due to a change from using IsAssignableFrom to is as part of a refactor. Corrected in pr #45

Allow custom multitenant stores and strategies to specify service lifetime

In 1.2 all multitenant stores and strategies are registered as singletons. This works fine for some cases, but other cases make more sense to have a scoped (or maybe even transient) lifetime, e.g. dbcontexts registered with AddDbContext are scoped.

WithStore<T>
WithStore<T>(ServiceLifetime lifetime, params object[] parameters)

The other WithStore and WithStrategy variants would add a similar lifetime parameter. The built in store and strategies would be configured (and documented) with the most sensible lifetime.

Switch to Apache 2.0 license

In order to better align with official .NET Core code this project should switch from an MIT license to an Apache 2.0 license.

Known Issues with Finbuckle.MultiTenant and ASP.NET Core Identity

Hello, this announcement is to describe known issues with Finbuckle.MultiTenant and ASP.NET Core Identity. The first two are specific to theIdentityDataIsolationSample project and the third is a bug in Identity itself. These items will be resolved future releases, but since some of them are dependent on ASP.NET Core I can't say for sure when that will be.

  1. The sample configures Google authentication as a social login provider, but fails to call WithRemoteAuthentication after AddMultiTenant. This results in a user not being set up with the correct tenant when forwarded back to the app from Google. To fix this add a call to WithRemoteAuthentication after AddMultiTenant in the ConfigureServices method.

  2. The Identity pages do not render correctly. This is due to the sample application using Bootstrap 4 for layout whereas Identity assumes Bootstrap 3 by default. ASP.NET Core 2.2 is expected to add support for both Bootstrap 3 & 4 to Identity.

  3. Failed authentication doesn't redirect to the correct sign in page. This is due to abug in Identity and can be worked around as described here.

1.2.0 release and upcoming new documentation/website

Hello,

1.2.0 as just released with improved support for ASP.NET Core Identity, a sample project for Identity, and several bug fixes.

Also just as an FYI I'm working on a new website and project documentation--hope to get that out soon.

Multiple external logins cause PK error in AspNetUserLogins

Hi Andrew - great project!

I am just using to explore ideas at present, stumbled upon what I think is a minor issue (read: easy fix) with external logins .. consider 2 tenants, with one external user, a customer of both tenants

The AspNetUserLogins table has a primary key defined on LoginProvider and ProviderKey only - so this external user can only exist once, associated to the first tenant only

The TenantId column is added to AspNetUserLogins as expected, and the query to find an existing external login does filter as expected WHERE TenantId = ? AND LoginProvider = ? AND ProviderKey = ? ...so not finding the user for the 2nd tenant, then attempting the failing insert (a duplicate primary key thing)

So the fix for me was just to drop/create this PK but now as a combination of LoginProvider, ProviderKey, and TenantId .. I just updated my database directly for now but will eventually add to a migration on the context

Thanks

API Sample

Will there be a sample of implementing a MultiTenant API?

Specifically an example of a MultiTenant scenario where there's a tenant database for tenant lookup and a database per tenant?

Clean up namespaces for a 2.0.0 release.

For an eventual 2.0 release I want to clean up the namespaces for code that is expected to be usable by the library user. Right now its a mix of Finbuckle.MultiTenant, Finbuckle.MultiTenant.AspNetcore, etc...
I'm leaning toward it all being Finbuckle.MultiTenant for simplicity.

Add MultiTenant support for generic IdentityDbContext<TUser, TRole, TKey, TUserLogin, TUserRole, TUserClaim, TRoleClaim, TUserToken>

Per: https://github.com/entrypointsoft/AspNet.Identity.EntityFramework6/blob/master/src/AspNet.Identity.EntityFramework6/IdentityDbContext.cs

An implementation supporting the more explicit generic class would be useful.

I've got a branch going for this locally that I'm playing with at the moment.

Edit: FYI, doesn't look like I have the ability to push branches up with the access token provided. I'll leave anything pertaining to an issue in the branch form: feature/{issue#}-{description} and submit PRs in the dev.

Forked. PR submitted (after failing to sign initial commit)

Strange bug regarding WithRemoteAuthentication

I am using a social login provider (google in this case) All works well with the following configuration

            services.AddMultiTenant()
                .WithStrategy<TenantStrategy>()
                .AddPerTenantCookieAuthentication()
                //.WithRemoteAuthentication()
                .AddPerTenantSocialLogins()
                ;

            services.Replace(ServiceDescriptor.Singleton<IAuthenticationSchemeProvider, MultiTenantAuthenticationSchemeProvider>());
            //services.Replace(ServiceDescriptor.Scoped<IAuthenticationService, MultiTenantAuthenticationService>());
            services.TryAddSingleton<IRemoteAuthenticationStrategy, RemoteAuthenticationStrategy>();

WithRemoteAuthentication has 3 methods it calls. For some reason the replacement for IAuthenticationService makes the 'signin-google' route redirect to the refererrer instead of the callbackurl.

BTW, it works for me as well without the .WithRemote, I guess is it because I am using a custom host strategy.

WRONG:
afbeelding

CORRECT (with middle call not made)
afbeelding

The challenge seems ok, but then again, the challenge 302 is not human readable. My fix for me is to remove the WithRemoteAuthentication altogether, as it works without for me, and I don't understand what it's doing.

I hope there is enough info here, netcore 2.1

Improve Host Strategy

Current host strategy only finds the first segment of the host. Would be better to allow more flexible options based on a template/regex to allow main domain, nth segment, or nth segment from the end.

Implementing template pattern such as *.tenant.xyz.? where:

  • tenant is a placeholder for the identifier
    • is a wildcard meaning 0 or more segments (including the period) and can only be on one side of the tenant placeholder
  • ? always means exactly 1 segment
  • xyz indicates any other non period characters, carried directly into the regex.

Creating new tenant and applying changes without restart

Hi,

Everytime I create a new tenant I have to restart the web application so the startup.cs would run again and the new tenant configuration could be applied.

If I create the tenant without restarting I can't access the new tenant website directly as I have to restart to be able to access it.

here is the code I'm using to create the new tenant.
// give him the code you're using

I tried to go through documentation but couldn't find the solution.

thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.