Skip to content

Commit

Permalink
Merge branch 'release/2.0.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
dazinator committed Jun 8, 2019
2 parents b799578 + 3d22669 commit 344e96a
Show file tree
Hide file tree
Showing 417 changed files with 110,557 additions and 25,266 deletions.
11 changes: 11 additions & 0 deletions GitVersion.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
major-version-bump-message: '\+semver:\s?(breaking|major)'
minor-version-bump-message: '\+semver:\s?(feature|minor)'
patch-version-bump-message: '\+semver:\s?(fix|patch)'
commit-message-incrementing: Enabled
branches:
feature:
mode: ContinuousDelivery
tag: useBranchName
increment: Inherit
prevent-increment-of-merged-branch-version: false
track-merge-target: false
173 changes: 141 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,141 @@
c# Dotnettency
Mutlitenancy library for dotnet applications.

| Branch | Build Status | Dotnettency Core Library | Middleware | Container | StructureMap |
| ------------- | ------------- | ----- | ----- | ----- | ----- |
| Master |[![Build status](https://ci.appveyor.com/api/projects/status/2xi1nts54u2hamv3/branch/master?svg=true)](https://ci.appveyor.com/project/dazinator/dotnettency/branch/master) | [![Dotnettency](https://img.shields.io/nuget/v/Dotnettency.svg)](https://www.nuget.org/packages/Dotnettency/) | [![MiddlewarePipeline](https://img.shields.io/nuget/v/Dotnettency.MiddlewarePipeline.svg)](https://www.nuget.org/packages/Dotnettency.MiddlewarePipeline/) | [![Container](https://img.shields.io/nuget/v/Dotnettency.Container.svg)](https://www.nuget.org/packages/Dotnettency.Container/) | [![StructureMap](https://img.shields.io/nuget/v/Dotnettency.Container.StructureMap.svg)](https://www.nuget.org/packages/Dotnettency.Container.StructureMap/) |
| Develop | [![Build status](https://ci.appveyor.com/api/projects/status/2xi1nts54u2hamv3/branch/develop?svg=true)](https://ci.appveyor.com/project/dazinator/dotnettency/branch/develop) | [![Dotnettency](https://img.shields.io/nuget/vpre/Dotnettency.svg)](https://www.nuget.org/packages/Dotnettency/) | [![MiddlewarePipeline](https://img.shields.io/nuget/vpre/Dotnettency.MiddlewarePipeline.svg)](https://www.nuget.org/packages/Dotnettency.MiddlewarePipeline/) | [![Container](https://img.shields.io/nuget/vpre/Dotnettency.Container.svg)](https://www.nuget.org/packages/Dotnettency.Container/) | [![StructureMap](https://img.shields.io/nuget/vpre/Dotnettency.Container.StructureMap.svg)](https://www.nuget.org/packages/Dotnettency.Container.StructureMap/) |



Heavily inspired by [saaskit](https://github.com/saaskit/saaskit)

See the [sample app for a full display of all the current features](https://github.com/dazinator/Dotnettency/tree/master/src/Dotnettency.Sample) or if you want to see MVC in action, checkout the [MVC sample](https://github.com/dazinator/Dotnettency/tree/develop/src/Sample.Mvc) which include:

- Tenant resolution
- Per Tenant Middleware Pipeline
- Per Tenant Containers
- Per Tenant HostingEnvironment

Tenant Resolution

Once configured in `startup.cs` you can:

- Inject `TTenant` directly (may block whilst resolving current tenant).
- Inject `Task<TTenant>` - Allows you to `await` the current `Tenant` (so non blocking). `Task<TTenant>` is convenient.
- Inject `ITenantAccessor<TTenant>`. This is similar to injecting `Task<Tenant>` in that it provides lazy access the current tenant in a non blocking way. For convenience it's now easier to just inject `Task<Tenant>` instead, unless you want a more descriptive API.
- Inject `ITenantShellAccessor<TTenant>` in order to access context for the currnet tenant, which is primarily used by:
- Extensions (such as Middleware, or Container) - which store things for the tenant in the `ITenantShellAccessor<TTenant>`'s concurrent property bag.
- Tenant Admin screens - if you need to "Restart" a tenant, then the idea is, you can resolve the `ITenantShellAccessor<TTenant>` and then use extension methods (provided by the dotnettency extensions such as Middleware pipeline, or Container) to allow you to control the state of the running tenant - for example to trigger rebuild of the tenant's container, or pipeline on the next request.


Notes: For details on what Per Tenant Hosting Environment does see the [README on the sample](https://github.com/dazinator/Dotnettency/tree/master/src/Dotnettency.Sample).
c# Dotnettency
Dotnettency is a library that provides features to enable Multi-Tenant applications using:
- ASP.NET Core
- OWIN

| Branch | Build Status |
| ------------- | ------------- |
| Master | [![Build status](https://ci.appveyor.com/api/projects/status/2xi1nts54u2hamv3/branch/master?svg=true)](https://ci.appveyor.com/project/dazinator/dotnettency/branch/master) |
| Develop | [![Build status](https://ci.appveyor.com/api/projects/status/2xi1nts54u2hamv3/branch/develop?svg=true)](https://ci.appveyor.com/project/dazinator/dotnettency/branch/develop) |

| Branch | Dotnettency Core Library | AspNetCore | Owin | EF Core | Tenant File System |
| ------------- | ------------- | ----- | ----- | ----- | ---- |
| Master | [![Dotnettency](https://img.shields.io/nuget/v/Dotnettency.svg)](https://www.nuget.org/packages/Dotnettency/) | [![AspNetCore](https://img.shields.io/nuget/v/Dotnettency.AspNetCore.svg)](https://www.nuget.org/packages/Dotnettency.AspNetCore/) | [![Owin](https://img.shields.io/nuget/v/Dotnettency.Owin.svg)](https://www.nuget.org/packages/Dotnettency.Owin/) | [![EF Core](https://img.shields.io/nuget/v/Dotnettency.EFCore.svg)](https://www.nuget.org/packages/Dotnettency.EFCore/) | [![Tenant FileSystem](https://img.shields.io/nuget/v/Dotnettency.TenantFileSystem.svg)](https://www.nuget.org/packages/Dotnettency.TenantFileSystem/) |
| Develop | [![Dotnettency](https://img.shields.io/nuget/vpre/Dotnettency.svg)](https://www.nuget.org/packages/Dotnettency/) | [![AspNetCore](https://img.shields.io/nuget/vpre/Dotnettency.AspNetCore.svg)](https://www.nuget.org/packages/Dotnettency.AspNetCore/) | [![Owin](https://img.shields.io/nuget/vpre/Dotnettency.Owin.svg)](https://www.nuget.org/packages/Dotnettency.Owin/) | [![EF Core](https://img.shields.io/nuget/vpre/Dotnettency.EFCore.svg)](https://www.nuget.org/packages/Dotnettency.EFCore/) | [![Tenant FileSystem](https://img.shields.io/nuget/vpre/Dotnettency.TenantFileSystem.svg)](https://www.nuget.org/packages/Dotnettency.TenantFileSystem/) |

| Branch | Autofac | StructureMap |
| ------------- | ------------- | ------------- |
| Master | [![Autofac](https://img.shields.io/nuget/v/Dotnettency.Container.Autofac.svg)](https://www.nuget.org/packages/Dotnettency.Container.Autofac/) | [![StructureMap](https://img.shields.io/nuget/v/Dotnettency.Container.StructureMap.svg)](https://www.nuget.org/packages/Dotnettency.Container.StructureMap/) |
| Develop | [![Autofac](https://img.shields.io/nuget/vpre/Dotnettency.Container.Autofac.svg)](https://www.nuget.org/packages/Dotnettency.Container.Autofac/) | [![StructureMap](https://img.shields.io/nuget/vpre/Dotnettency.Container.StructureMap.svg)](https://www.nuget.org/packages/Dotnettency.Container.StructureMap/) |


Inspired by [saaskit](https://github.com/saaskit/saaskit)

## Resources

- Tutorial series here: http://darrelltunnell.net/tutorial/creating-modular-multi-tenant-asp-net-core-application-with-dotnettency/
- Various samples here: https://github.com/dazinator/Dotnettency.Samples
- More extensive [sample app for a full display of all the current features](https://github.com/dazinator/Dotnettency/tree/master/src/Dotnettency.Sample) or if you want to see MVC in action, checkout the [MVC sample](https://github.com/dazinator/Dotnettency/tree/develop/src/Sample.Mvc)

## Features

### Tenant resolution

You can define how you want to identify the current tenant, i.e using a url scheme, cookie, or any of your custom logic.
You can then access the current tenant through dependency injection in your app.

### Tenant Middleware Pipelines
In your web application (OWIN or ASP.NET Core), when the web server recieves a request, it typically runs it through a single "middleware pipeline".
`Dotnettency` allows you to have a lazily initialised "Tenant Middleware Pipeline" created for each distinct tenant. In the tenant specific middleware pipeline, you can choose to include middleware conditionally based on current tenant information.
For example, for one tenant, you may use Facebook Authentication middleware, where as for another you might not want that middleware enabled.

### Tenant Containers / Services
In ASP.NET Core applications (Dotnettency also allows you to achieve this in OWIN applications even though OWIN doesn't cater for this pattern out of the box), you configure a global set of services on startup for dependency injection purposes.
At the start of a request, ASP.NET Core middleware creates a scoped version of those services to satisfy that request.
`Dotnettency` goes a step further, by allowing you to register services for each specific tenant. Dotnettency middleware then
provides an `IServiceProvider` scoped to the request for the current tenant. This means services that are typically injected into your classes during a request can now be tenant specific.
This is useful if, for example, you want one tenant to use a different `IPaymentProvider` etc from another based on tenant settings etc.

### Tenant File System
Notes: For more in depth details on what Per Tenant File System is, see the [README on the sample](https://github.com/dazinator/Dotnettency/tree/master/src/Dotnettency.Sample).

Allows you to configure an `IFileProvider` that returns files from a virtual directory build for the current tenant.
For example, tenant `foo` might want to access a file `/bar.txt` which exists for them, but when tenant `bar` tries to access `/bar.txt` it doesn't exist for them - because each tenant has it's own distinct virtual directory.
Tenant virtual directories can overlap by sharing access to common directories / files.

## Tenant Injection

Once configured in `startup.cs` you can resolve the current tenant in any one of the following ways:

- Inject `TTenant` directly (may block whilst resolving current tenant).
- Inject `Task<TTenant>` - Allows you to `await` the current `Tenant` (so non blocking). `Task<TTenant>` is convenient.
- Inject `ITenantAccessor<TTenant>`. This is similar to injecting `Task<Tenant>` in that it provides lazy access the current tenant in a non blocking way. For convenience it's now easier to just inject `Task<Tenant>` instead, unless you want a more descriptive API.

## Tenant Restart (New in v2.0.0)

You can `Restart` a tenant. This does not stop the web application, or interfere with other tenants.
When you trigger a `Restart` of a tenant, it means the current tenants `TenantShell` (and all state, such as Services, MiddlewarePipeline etc) are disposed of.
Once the Restart has finished, it means the next http request to that tenant will result in the tenant intialising again from scratch.
This is useful for example, if you register services or middleware based on some settings, and you want to allow the settings to be changed for the tenant and therefore services middleware pipeline to be rebuilt based on latest config.
It is also useful if you have a plugin based architecture, and you want to allow tenants to install plugins whilst the system is running.

- Tenant Container will be re-built (if you are usijng tenant services the method you use to register services for the current tenant will be re-rexecuted.)
- Tenant Middleware Pipeline will be re-built (if you are using tenant middleware pipeline, it will be rebuilt - you'll have a chance to include additional middlewares etc.)

For sample usage, see the Sample.AspNetCore30.RazorPages sample in this solution, in partcular the Pages/Gicrosoft/Index.cshtml page.

Injext `ITenantShellRestarter<Tenant>` and invoke the `Restart()` method:

```
public class IndexModel : PageModel
{
public bool IsRestarted { get; set; }
public void OnGet()
{
}
public async Task OnPost([FromServices]ITenantShellRestarter<Tenant> restarter)
{
await restarter.Restart();
IsRestarted = true;
this.Redirect("/");
}
}
```

and corresponding razor page:

```
@page
@using Sample.Pages.T1
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
<div class="text-center">
<h1>Tenant Gicrosoft Razor Pages!</h1>
<form method="post">
@{
if (!@Model.IsRestarted)
{
<button asp-page="Index">Restart Tenant</button>
}
else
{
<button disabled asp-page="Index">Restart Tenant</button>
<p>Tenant has been restarted, the next request will result in Tenant Container being rebuilt, and tenant middleware pipeline being re-initialised.</p>
}
}
</form>
</div>
```
## Tenant Shell Injection

The `TenantShell` stores the context for a Tenant, such as it's `Container` and it's `MiddlewarePipeline`.
It's stored in a cache, and is evicted if the tenant is Restarted.
You probably won't need to use it directly, but if you want you can do so.

- Inject `ITenantShellAccessor<TTenant>` to access the TenantShell for the current tenant.
- Extensions (such as Middleware, or Container) - store things for the tenant in it's concurrent property bag. You can get at these properties if you know the keys.
- You can also register callbacks that will be invoked when the TenantShell is disposed of - this happens when the tenant is restarted for example.

Another way to register code that will run when the tenant is restarted, is to use TenantServices - add a disposable singleton service the tenant's container.
When the tenant is disposed of, it's container will be disposed of, and your disposable service will be disposed of - depending upon your needs this hook might suffice.
54 changes: 28 additions & 26 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
# environment variables
environment:
path_to_sln: src/src.sln
skip_tags: true
install:
- choco install gitversion.portable -pre -y
- cinst gitlink -version 2.4.1 -y
image: Visual Studio 2017
assembly_info:
patch: false
before_build:
- ps: gitversion /l console /output buildserver
- cmd: msbuild %path_to_sln% /t:restore /p:PackageVersion=%GitVersion_NuGetVersion%
build:
project: '%path_to_sln%'
verbosity: minimal
after_build:
- cmd: gitlink . -u https://github.com/%APPVEYOR_REPO_NAME% -b %APPVEYOR_REPO_BRANCH% -s %APPVEYOR_REPO_COMMIT% -f %path_to_sln%
- cmd: msbuild %path_to_sln% /t:Pack /p:PackageVersion=%GitVersion_NuGetVersion%
artifacts:
- path: /src/**/*.nupkg
deploy:
provider: NuGet
api_key:
secure: u8JpW5kkti8pMi+ra2QcXTJPhkHCA8pkKSiiZOJbcS/vFVHNvF3W8qw1Fy2If6a7
skip_symbols: false
# environment variables
environment:
path_to_sln: src/src.sln
skip_tags: true
install:
- choco install gitversion.portable -pre -y
- cinst gitlink -version 2.4.1 -y
- cinst dotnetcore-sdk --pre
- dotnet new -i Microsoft.AspNetCore.Blazor.Templates
image: Visual Studio 2019 Preview
assembly_info:
patch: false
before_build:
- ps: gitversion /l console /output buildserver
- cmd: msbuild %path_to_sln% /t:restore /p:PackageVersion=%GitVersion_FullSemVer%
build:
project: '%path_to_sln%'
verbosity: minimal
after_build:
- cmd: gitlink . -u https://github.com/%APPVEYOR_REPO_NAME% -b %APPVEYOR_REPO_BRANCH% -s %APPVEYOR_REPO_COMMIT% -f %path_to_sln%
- cmd: msbuild %path_to_sln% /t:Pack /p:PackageVersion=%GitVersion_FullSemVer%
artifacts:
- path: /src/**/*.nupkg
deploy:
provider: NuGet
api_key:
secure: u8JpW5kkti8pMi+ra2QcXTJPhkHCA8pkKSiiZOJbcS/vFVHNvF3W8qw1Fy2If6a7
skip_symbols: false
artifact: /.*\.nupkg/
64 changes: 64 additions & 0 deletions src/Dotnettency.AspNetCore.Modules/DelegateModuleFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using Dotnettency.Container;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Dotnettency.Modules
{
public class DelegateModuleFactory<TTenant, TModule> : IModuleFactory<TTenant, TModule>
where TTenant : class
where TModule : IModule
{

private readonly Func<ITenantContainerAdaptor, TTenant, IEnumerable<TModule>> _getModulesDelegate;

public DelegateModuleFactory(Func<ITenantContainerAdaptor, TTenant, IEnumerable<TModule>> getModulesDelegate)
{
_getModulesDelegate = getModulesDelegate;
}

public Task<IEnumerable<TModule>> GetModulesForTenant(ITenantContainerAdaptor container, TTenant tenant)
{
return Task.Run(() =>
{
return _getModulesDelegate(container, tenant);
});
}
}

//public class ModuleShell<TModule>
// where TModule : IModule
//{

// private readonly Func<ITenantContainerAdaptor> _moduleContainerFactory;

// public ModuleShell(IModule module, Func<ITenantContainerAdaptor> moduleContainerFactory)
// {
// Module = module;
// _moduleContainerFactory = moduleContainerFactory;
// }

// public IModule Module { get; }

// public void Restart()
// {
// if (Container != null)
// {
// Container.Dispose();
// Container = null;
// }
// // Get the module container
// Container = _moduleContainerFactory();



// }

// /// <summary>
// /// The curent container that is used for this modules services.
// /// </summary>
// /// <remarks>Could be the Application, Tenant, or Module container depending on how modules are being isolated.</remarks>
// private ITenantContainerAdaptor Container { get; set; }

//}
}
24 changes: 24 additions & 0 deletions src/Dotnettency.AspNetCore.Modules/DelegateModuleShellFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Dotnettency.Modules;
using System;

namespace Dotnettency
{
public class DelegateModuleShellFactory<TTenant, TModule> : IModuleShellFactory<TTenant, TModule>
where TTenant : class
where TModule : IModule
{

private readonly Func<TTenant, TModule, ModuleShell<TModule>> _factoryDelegate;

public DelegateModuleShellFactory(Func<TTenant, TModule, ModuleShell<TModule>> factoryDelegate)
{
_factoryDelegate = factoryDelegate;
}

public ModuleShell<TModule> GetModuleShell(TTenant tenant, TModule module)
{
return _factoryDelegate(tenant, module);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard1.3</TargetFramework>
</PropertyGroup>

<ItemGroup>
<Compile Remove="DelegateModuleFactory.cs" />
<Compile Remove="DelegateModuleShellFactory.cs" />
<Compile Remove="IModuleFactory.cs" />
<Compile Remove="IModuleShellFactory.cs" />
<Compile Remove="ModuleContainerBuilderOptionsExtensions.cs" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Routing" Version="1.0.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Dotnettency.AspNetCore\Dotnettency.AspNetCore.csproj" />
<ProjectReference Include="..\Dotnettency\Dotnettency.csproj" />
</ItemGroup>

</Project>
9 changes: 9 additions & 0 deletions src/Dotnettency.AspNetCore.Modules/IModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Dotnettency.AspNetCore.Modules
{
/// <summary>
/// A marker interface for all modules.
/// </summary>
public interface IModule
{
}
}
Loading

0 comments on commit 344e96a

Please sign in to comment.