|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
@ -0,0 +1,54 @@ |
|||||
|
# 微服务演示,项目状态和路线图 |
||||
|
|
||||
|
在ABP vNext上的[第一个公告](https://cn.abp.io/blog/abp/Abp-vNext-Announcement)之后,我们对代码库进行了很多改进([GitHub存储库](https://github.com/abpframework/abp)上的1100多次提交).我们已经创建了功能,示例,文档等等.在这篇文章中,我想告诉你一些新闻和项目的状态. |
||||
|
|
||||
|
## 微服务演示解决方案 |
||||
|
|
||||
|
ABP框架的主要目标之一是提供[创建微服务解决方案的便利基础设施](https://cn.abp.io/documents/abp/latest/Microservice-Architecture). |
||||
|
|
||||
|
我们一直在努力开发微服务解决方案演示.初始版本已完成并[文档化](https://cn.abp.io/documents/abp/latest/Samples/Microservice-Demo).该示例解决方案旨在演示一个简单而完整的微服务解决方案; |
||||
|
|
||||
|
- 具有多个独立的,可自我部署的**微服务**. |
||||
|
- 多个**Web应用程序**,每个都使用不同的API网关. |
||||
|
- 使用[Ocelot](https://github.com/ThreeMammals/Ocelot)库开发了多个**网关** / BFF(后端为前端(Backend for Frontends)). |
||||
|
- 使用[IdentityServer](https://identityserver.io/)框架开发**身份验证服务**.它也是一个带有必要UI的SSO(单点登录)应用程序. |
||||
|
- 有**多个数据库**.一些微服务有自己的数据库,而一些服务/应用程序共享一个数据库(以演示不同的用例). |
||||
|
- 具有不同类型的数据库:**SQL Server**(使用**Entity Framework Core** ORM)和**MongoDB**. |
||||
|
- 有一个**控制台应用程序**来显示通过身份验证使用服务的最简单方法. |
||||
|
- 使用[Redis](https://redis.io/)进行**分布式缓存**. |
||||
|
- 使用[RabbitMQ](https://www.rabbitmq.com/)进行服务到服务(service-to-service)的**消息传递**. |
||||
|
- 使用[Docker](https://www.docker.com/)和[Kubernates](https://kubernetes.io/)**部署**并运行所有服务和应用程序. |
||||
|
- 使用[Elasticsearch](https://www.elastic.co/products/elasticsearch)和[Kibana](https://www.elastic.co/products/kibana)存储和可视化日志(使用[Serilog](https://serilog.net/)编写). |
||||
|
|
||||
|
有关解决方案的详细说明,请参阅[其文档](https://cn.abp.io/documents/abp/latest/Samples/Microservice-Demo). |
||||
|
|
||||
|
## 改进/功能 |
||||
|
|
||||
|
我们已经开发了许多功能,包括**分布式事件总线**(与RabbitMQ集成),**IdentityServer4集成**以及几乎所有功能的增强.我们不断重构和添加测试,以使框架更稳定和生产就绪.它正在[快速增长](https://github.com/abpframework/abp/graphs/contributors). |
||||
|
|
||||
|
## 路线图 |
||||
|
|
||||
|
在第一个稳定版本(v1.0)之前还有很多工作要做.您可以在GitHub仓库上看到[优先的积压项目](https://github.com/abpframework/abp/issues?q=is%3Aopen+is%3Aissue+milestone%3ABacklog). |
||||
|
|
||||
|
根据我们的估计,我们计划在2019年第二季度(可能在五月或六月)发布v1.0.所以,不用等待太长时间了.我们也对第一个稳定版本感到非常兴奋. |
||||
|
|
||||
|
我们还将完善[文档](https://cn.abp.io/documents/abp/latest),因为它现在还远未完成. |
||||
|
|
||||
|
第一个版本可能不包含SPA模板.但是,如果可能的话,我们想要准备一个简单些的.SPA框架还没有确定下来.备选有:**Angular,React和Blazor**.请将您的想法写为对此帖的评论. |
||||
|
|
||||
|
## 中文网 |
||||
|
|
||||
|
中国有一个大型的ABP社区.他们创建了一个中文版的abp.io网站:https://cn.abp.io/. 他们一直在保持更新.感谢中国的开发人员,特别是[Liming Ma](https://github.com/maliming). |
||||
|
|
||||
|
## NDC {London} 2019 |
||||
|
|
||||
|
很高兴作为合作伙伴参加[NDC {London}](https://ndc-london.com/)2019 .我们已经与许多开发人员讨论过当前的ASP.NET Boilerplate和ABP vNext,我们得到了很好的反馈. |
||||
|
|
||||
|
我们还有机会与[Scott Hanselman](https://twitter.com/shanselman)和[Jon Galloway](https://twitter.com/jongalloway)交谈.他们参观了我们的展位,我们谈到了ABP vNext的想法.他们喜欢新的ABP框架的功能,方法和目标.在twitter上查看一些照片和评论: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
## 跟上步伐 |
||||
|
|
||||
|
* 您可以标星并关注**GitHub**存储库:https://github.com/abpframework/abp |
||||
|
* 您可以关注官方**Twitter**帐户获取新闻:https://twitter.com/abpframework |
||||
|
After Width: | Height: | Size: 477 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 154 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
@ -0,0 +1,40 @@ |
|||||
|
using JetBrains.Annotations; |
||||
|
using Microsoft.AspNetCore.Http; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
|
namespace Volo.Abp.AspNetCore.MultiTenancy |
||||
|
{ |
||||
|
public class HttpContextTenantResolveResultAccessor : ITenantResolveResultAccessor, ITransientDependency |
||||
|
{ |
||||
|
[CanBeNull] |
||||
|
public TenantResolveResult Result |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
if (_httpContextAccessor.HttpContext == null) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
return _httpContextAccessor.HttpContext.Items[""] as TenantResolveResult; |
||||
|
} |
||||
|
set |
||||
|
{ |
||||
|
if (_httpContextAccessor.HttpContext == null) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
_httpContextAccessor.HttpContext.Items[""] = value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private readonly IHttpContextAccessor _httpContextAccessor; |
||||
|
|
||||
|
public HttpContextTenantResolveResultAccessor(IHttpContextAccessor httpContextAccessor) |
||||
|
{ |
||||
|
_httpContextAccessor = httpContextAccessor; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
|
namespace Volo.Abp.AspNetCore.MultiTenancy |
||||
|
{ |
||||
|
public interface ITenantResolveResultAccessor |
||||
|
{ |
||||
|
TenantResolveResult Result { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -1,17 +0,0 @@ |
|||||
@using Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy.Components.TenantSwitch |
|
||||
@model TenantSwitchViewComponent.TenantSwitchViewModel |
|
||||
@if (!Model.CurrentUser.IsAuthenticated) |
|
||||
{ |
|
||||
<li class="nav-item"> |
|
||||
<a abp-button="Link" id="TenantSwitchToolbarLink" href="#"> |
|
||||
@if (Model.Tenant == null) |
|
||||
{ |
|
||||
<text>@@host</text> |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
<text>@@@Model.Tenant.Name</text> |
|
||||
} |
|
||||
</a> |
|
||||
</li> |
|
||||
} |
|
||||
@ -1,51 +0,0 @@ |
|||||
using System.Threading.Tasks; |
|
||||
using Microsoft.AspNetCore.Mvc; |
|
||||
using Volo.Abp.MultiTenancy; |
|
||||
using Volo.Abp.Users; |
|
||||
|
|
||||
namespace Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy.Components.TenantSwitch |
|
||||
{ |
|
||||
public class TenantSwitchViewComponent : AbpViewComponent |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// -1,000,000
|
|
||||
/// </summary>
|
|
||||
public const int Order = -1_000_000; |
|
||||
|
|
||||
protected ITenantStore TenantStore { get; } |
|
||||
protected ICurrentTenant CurrentTenant { get; } |
|
||||
protected ICurrentUser CurrentUser { get; } |
|
||||
|
|
||||
public TenantSwitchViewComponent( |
|
||||
ITenantStore tenantStore, |
|
||||
ICurrentTenant currentTenant, |
|
||||
ICurrentUser currentUser) |
|
||||
{ |
|
||||
TenantStore = tenantStore; |
|
||||
CurrentTenant = currentTenant; |
|
||||
CurrentUser = currentUser; |
|
||||
} |
|
||||
|
|
||||
public async Task<IViewComponentResult> InvokeAsync() |
|
||||
{ |
|
||||
var model = new TenantSwitchViewModel |
|
||||
{ |
|
||||
CurrentUser = CurrentUser |
|
||||
}; |
|
||||
|
|
||||
if (CurrentTenant.Id.HasValue) |
|
||||
{ |
|
||||
model.Tenant = await TenantStore.FindAsync(CurrentTenant.GetId()); |
|
||||
} |
|
||||
|
|
||||
return View("~/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Components/TenantSwitch/Default.cshtml", model); |
|
||||
} |
|
||||
|
|
||||
public class TenantSwitchViewModel |
|
||||
{ |
|
||||
public TenantInfo Tenant { get; set; } |
|
||||
|
|
||||
public ICurrentUser CurrentUser { get; set; } |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,3 +0,0 @@ |
|||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers |
|
||||
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap |
|
||||
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling |
|
||||
@ -1,9 +1,11 @@ |
|||||
{ |
{ |
||||
"culture": "en", |
"culture": "en", |
||||
"texts": { |
"texts": { |
||||
"GivenTenantIsNotAvailable": "Given tenant is not available: {0}", |
"GivenTenantIsNotAvailable": "Given tenant is not available: {0}", |
||||
"SwitchTenant": "Switch tenant", |
"Tenant": "Tenant", |
||||
|
"Switch": "switch", |
||||
"Name": "Name", |
"Name": "Name", |
||||
"SwitchTenantHint": "Leave the name field blank to switch to the host side." |
"SwitchTenantHint": "Leave the name field blank to switch to the host side.", |
||||
|
"NotSelected": "Not selected" |
||||
} |
} |
||||
} |
} |
||||
@ -1,21 +0,0 @@ |
|||||
using System.Threading.Tasks; |
|
||||
using Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy.Components.TenantSwitch; |
|
||||
using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Toolbars; |
|
||||
|
|
||||
namespace Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy |
|
||||
{ |
|
||||
public class MultiTenancyToolbarContributor : IToolbarContributor |
|
||||
{ |
|
||||
public Task ConfigureToolbarAsync(IToolbarConfigurationContext context) |
|
||||
{ |
|
||||
if (context.Toolbar.Name != StandardToolbars.Main) |
|
||||
{ |
|
||||
return Task.CompletedTask; |
|
||||
} |
|
||||
|
|
||||
context.Toolbar.Items.Add(new ToolbarItem(typeof(TenantSwitchViewComponent), TenantSwitchViewComponent.Order)); |
|
||||
|
|
||||
return Task.CompletedTask; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,48 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using JetBrains.Annotations; |
||||
|
|
||||
|
namespace Volo.Abp.Data |
||||
|
{ |
||||
|
public class DataSeedContext |
||||
|
{ |
||||
|
public Guid? TenantId { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets/sets a key-value on the <see cref="Properties"/>.
|
||||
|
/// </summary>
|
||||
|
/// <param name="name">Name of the property</param>
|
||||
|
/// <returns>
|
||||
|
/// Returns the value in the <see cref="Properties"/> dictionary by given <see cref="name"/>.
|
||||
|
/// Returns null if given <see cref="name"/> is not present in the <see cref="Properties"/> dictionary.
|
||||
|
/// </returns>
|
||||
|
[CanBeNull] |
||||
|
public object this[string name] |
||||
|
{ |
||||
|
get => Properties.GetOrDefault(name); |
||||
|
set => Properties[name] = value; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Can be used to get/set custom properties.
|
||||
|
/// </summary>
|
||||
|
[NotNull] |
||||
|
public Dictionary<string, object> Properties { get; } |
||||
|
|
||||
|
public DataSeedContext(Guid? tenantId = null) |
||||
|
{ |
||||
|
TenantId = tenantId; |
||||
|
Properties = new Dictionary<string, object>(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sets a property in the <see cref="Properties"/> dictionary.
|
||||
|
/// This is a shortcut for nested calls on this object.
|
||||
|
/// </summary>
|
||||
|
public virtual DataSeedContext WithProperty(string key, object value) |
||||
|
{ |
||||
|
Properties[key] = value; |
||||
|
return this; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using Volo.Abp.Collections; |
||||
|
|
||||
|
namespace Volo.Abp.Data |
||||
|
{ |
||||
|
public class DataSeedContributorList : TypeList<IDataSeedContributor> |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
namespace Volo.Abp.Data |
||||
|
{ |
||||
|
public class DataSeedOptions |
||||
|
{ |
||||
|
public DataSeedContributorList Contributors { get; } |
||||
|
|
||||
|
public DataSeedOptions() |
||||
|
{ |
||||
|
Contributors = new DataSeedContributorList(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace Volo.Abp.Data |
||||
|
{ |
||||
|
//TODO: Create a Volo.Abp.Data.Seeding namespace?
|
||||
|
public class DataSeeder : IDataSeeder, ITransientDependency |
||||
|
{ |
||||
|
protected IHybridServiceScopeFactory ServiceScopeFactory { get; } |
||||
|
protected DataSeedOptions Options { get; } |
||||
|
|
||||
|
public DataSeeder( |
||||
|
IOptions<DataSeedOptions> options, |
||||
|
IHybridServiceScopeFactory serviceScopeFactory) |
||||
|
{ |
||||
|
ServiceScopeFactory = serviceScopeFactory; |
||||
|
Options = options.Value; |
||||
|
} |
||||
|
|
||||
|
public async Task SeedAsync(DataSeedContext context) |
||||
|
{ |
||||
|
using (var scope = ServiceScopeFactory.CreateScope()) |
||||
|
{ |
||||
|
foreach (var contributorType in Options.Contributors) |
||||
|
{ |
||||
|
var contributor = (IDataSeedContributor) scope |
||||
|
.ServiceProvider |
||||
|
.GetRequiredService(contributorType); |
||||
|
|
||||
|
await contributor.SeedAsync(context); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Abp.Data |
||||
|
{ |
||||
|
public static class DataSeederExtensions |
||||
|
{ |
||||
|
public static Task SeedAsync(this IDataSeeder seeder, Guid? tenantId = null) |
||||
|
{ |
||||
|
return seeder.SeedAsync(new DataSeedContext(tenantId)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Abp.Data |
||||
|
{ |
||||
|
public interface IDataSeedContributor |
||||
|
{ |
||||
|
Task SeedAsync(DataSeedContext context); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Abp.Data |
||||
|
{ |
||||
|
public interface IDataSeeder |
||||
|
{ |
||||
|
Task SeedAsync(DataSeedContext context); |
||||
|
} |
||||
|
} |
||||
@ -1,15 +0,0 @@ |
|||||
using Volo.Abp.Data; |
|
||||
using Volo.Abp.Modularity; |
|
||||
using Volo.Abp.Security; |
|
||||
|
|
||||
namespace Volo.Abp.MultiTenancy |
|
||||
{ |
|
||||
[DependsOn( |
|
||||
typeof(AbpDataModule), |
|
||||
typeof(AbpSecurityModule) |
|
||||
)] |
|
||||
public class AbpMultiTenancyAbstractionsModule : AbpModule //TODO: Rename to AbpMultiTenancyModule?
|
|
||||
{ |
|
||||
|
|
||||
} |
|
||||
} |
|
||||
@ -1,21 +0,0 @@ |
|||||
using System.Threading; |
|
||||
using Volo.Abp.DependencyInjection; |
|
||||
|
|
||||
namespace Volo.Abp.MultiTenancy |
|
||||
{ |
|
||||
public class AsyncLocalCurrentTenantIdAccessor : ICurrentTenantIdAccessor, ISingletonDependency |
|
||||
{ |
|
||||
public TenantIdWrapper Current |
|
||||
{ |
|
||||
get => _currentScope.Value; |
|
||||
set => _currentScope.Value = value; |
|
||||
} |
|
||||
|
|
||||
private readonly AsyncLocal<TenantIdWrapper> _currentScope; |
|
||||
|
|
||||
public AsyncLocalCurrentTenantIdAccessor() |
|
||||
{ |
|
||||
_currentScope = new AsyncLocal<TenantIdWrapper>(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,30 +0,0 @@ |
|||||
using System; |
|
||||
using System.Linq; |
|
||||
using System.Threading.Tasks; |
|
||||
using Microsoft.Extensions.Options; |
|
||||
using Volo.Abp.DependencyInjection; |
|
||||
|
|
||||
namespace Volo.Abp.MultiTenancy.ConfigurationStore |
|
||||
{ |
|
||||
//TODO: Move to another package.
|
|
||||
[Dependency(TryRegister = true)] |
|
||||
public class ConfigurationTenantStore : ITenantStore, ITransientDependency |
|
||||
{ |
|
||||
private readonly ConfigurationTenantStoreOptions _options; |
|
||||
|
|
||||
public ConfigurationTenantStore(IOptionsSnapshot<ConfigurationTenantStoreOptions> options) |
|
||||
{ |
|
||||
_options = options.Value; |
|
||||
} |
|
||||
|
|
||||
public Task<TenantInfo> FindAsync(string name) |
|
||||
{ |
|
||||
return Task.FromResult(_options.Tenants.FirstOrDefault(t => t.Name == name)); |
|
||||
} |
|
||||
|
|
||||
public Task<TenantInfo> FindAsync(Guid id) |
|
||||
{ |
|
||||
return Task.FromResult(_options.Tenants.FirstOrDefault(t => t.Id == id)); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,12 +0,0 @@ |
|||||
namespace Volo.Abp.MultiTenancy.ConfigurationStore |
|
||||
{ |
|
||||
public class ConfigurationTenantStoreOptions |
|
||||
{ |
|
||||
public TenantInfo[] Tenants { get; set; } |
|
||||
|
|
||||
public ConfigurationTenantStoreOptions() |
|
||||
{ |
|
||||
Tenants = new TenantInfo[0]; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,34 +0,0 @@ |
|||||
using System; |
|
||||
using Volo.Abp.DependencyInjection; |
|
||||
|
|
||||
namespace Volo.Abp.MultiTenancy |
|
||||
{ |
|
||||
public class CurrentTenant : ICurrentTenant, ITransientDependency |
|
||||
{ |
|
||||
public virtual bool IsAvailable => Id.HasValue; |
|
||||
|
|
||||
public virtual Guid? Id => _currentTenantIdAccessor.Current?.TenantId; |
|
||||
|
|
||||
private readonly ICurrentTenantIdAccessor _currentTenantIdAccessor; |
|
||||
|
|
||||
public CurrentTenant(ICurrentTenantIdAccessor currentTenantIdAccessor) |
|
||||
{ |
|
||||
_currentTenantIdAccessor = currentTenantIdAccessor; |
|
||||
} |
|
||||
|
|
||||
public IDisposable Change(Guid? id) |
|
||||
{ |
|
||||
return SetCurrent(id); |
|
||||
} |
|
||||
|
|
||||
private IDisposable SetCurrent(Guid? tenantId) |
|
||||
{ |
|
||||
var parentScope = _currentTenantIdAccessor.Current; |
|
||||
_currentTenantIdAccessor.Current = new TenantIdWrapper(tenantId); |
|
||||
return new DisposeAction(() => |
|
||||
{ |
|
||||
_currentTenantIdAccessor.Current = parentScope; |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,13 +0,0 @@ |
|||||
namespace Volo.Abp.MultiTenancy |
|
||||
{ |
|
||||
/* Uses TenantScopeTenantInfoWrapper instead of TenantInfo because being null of Current is different that being null of Current.Tenant. |
|
||||
* A null Current indicates that we haven't set it explicitly. |
|
||||
* A null Current.Tenant indicates that we have set null tenant value explicitly. |
|
||||
* A non-null Current.Tenant indicates that we have set a tenant value explicitly. |
|
||||
*/ |
|
||||
|
|
||||
public interface ICurrentTenantIdAccessor |
|
||||
{ |
|
||||
TenantIdWrapper Current { get; set; } |
|
||||
} |
|
||||
} |
|
||||
@ -1,18 +0,0 @@ |
|||||
using System; |
|
||||
|
|
||||
namespace Volo.Abp.MultiTenancy |
|
||||
{ |
|
||||
public class TenantIdWrapper |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Null indicates the host.
|
|
||||
/// Not null value for a tenant.
|
|
||||
/// </summary>
|
|
||||
public Guid? TenantId { get; } |
|
||||
|
|
||||
public TenantIdWrapper(Guid? tenantId) |
|
||||
{ |
|
||||
TenantId = tenantId; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,21 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.Data; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.MultiTenancy.ConfigurationStore; |
||||
|
using Volo.Abp.Security; |
||||
|
|
||||
|
namespace Volo.Abp.MultiTenancy |
||||
|
{ |
||||
|
[DependsOn( |
||||
|
typeof(AbpDataModule), |
||||
|
typeof(AbpSecurityModule) |
||||
|
)] |
||||
|
public class AbpMultiTenancyModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
var configuration = context.Services.GetConfiguration(); |
||||
|
Configure<DefaultTenantStoreOptions>(configuration); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
using System.Threading; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace Volo.Abp.MultiTenancy |
||||
|
{ |
||||
|
public class AsyncLocalCurrentTenantAccessor : ICurrentTenantAccessor, ISingletonDependency |
||||
|
{ |
||||
|
public BasicTenantInfo Current |
||||
|
{ |
||||
|
get => _currentScope.Value; |
||||
|
set => _currentScope.Value = value; |
||||
|
} |
||||
|
|
||||
|
private readonly AsyncLocal<BasicTenantInfo> _currentScope; |
||||
|
|
||||
|
public AsyncLocalCurrentTenantAccessor() |
||||
|
{ |
||||
|
_currentScope = new AsyncLocal<BasicTenantInfo>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
using System; |
||||
|
using JetBrains.Annotations; |
||||
|
|
||||
|
namespace Volo.Abp.MultiTenancy |
||||
|
{ |
||||
|
public class BasicTenantInfo |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Null indicates the host.
|
||||
|
/// Not null value for a tenant.
|
||||
|
/// </summary>
|
||||
|
[CanBeNull] |
||||
|
public Guid? TenantId { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Name of the tenant if <see cref="TenantId"/> is not null.
|
||||
|
/// </summary>
|
||||
|
[CanBeNull] |
||||
|
public string Name { get; } |
||||
|
|
||||
|
public BasicTenantInfo(Guid? tenantId, string name = null) |
||||
|
{ |
||||
|
TenantId = tenantId; |
||||
|
Name = name; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace Volo.Abp.MultiTenancy.ConfigurationStore |
||||
|
{ |
||||
|
[Dependency(TryRegister = true)] |
||||
|
public class DefaultTenantStore : ITenantStore, ITransientDependency |
||||
|
{ |
||||
|
private readonly DefaultTenantStoreOptions _options; |
||||
|
|
||||
|
public DefaultTenantStore(IOptionsSnapshot<DefaultTenantStoreOptions> options) |
||||
|
{ |
||||
|
_options = options.Value; |
||||
|
} |
||||
|
|
||||
|
public Task<TenantConfiguration> FindAsync(string name) |
||||
|
{ |
||||
|
return Task.FromResult(_options.Tenants?.FirstOrDefault(t => t.Name == name)); |
||||
|
} |
||||
|
|
||||
|
public Task<TenantConfiguration> FindAsync(Guid id) |
||||
|
{ |
||||
|
return Task.FromResult(_options.Tenants?.FirstOrDefault(t => t.Id == id)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
namespace Volo.Abp.MultiTenancy.ConfigurationStore |
||||
|
{ |
||||
|
public class DefaultTenantStoreOptions |
||||
|
{ |
||||
|
public TenantConfiguration[] Tenants { get; set; } |
||||
|
|
||||
|
public DefaultTenantStoreOptions() |
||||
|
{ |
||||
|
Tenants = new TenantConfiguration[0]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace Volo.Abp.MultiTenancy |
||||
|
{ |
||||
|
public class CurrentTenant : ICurrentTenant, ITransientDependency |
||||
|
{ |
||||
|
public virtual bool IsAvailable => Id.HasValue; |
||||
|
|
||||
|
public virtual Guid? Id => _currentTenantAccessor.Current?.TenantId; |
||||
|
|
||||
|
public string Name => _currentTenantAccessor.Current?.Name; |
||||
|
|
||||
|
private readonly ICurrentTenantAccessor _currentTenantAccessor; |
||||
|
|
||||
|
public CurrentTenant(ICurrentTenantAccessor currentTenantAccessor) |
||||
|
{ |
||||
|
_currentTenantAccessor = currentTenantAccessor; |
||||
|
} |
||||
|
|
||||
|
public IDisposable Change(Guid? id, string name = null) |
||||
|
{ |
||||
|
return SetCurrent(id, name); |
||||
|
} |
||||
|
|
||||
|
private IDisposable SetCurrent(Guid? tenantId, string name = null) |
||||
|
{ |
||||
|
var parentScope = _currentTenantAccessor.Current; |
||||
|
_currentTenantAccessor.Current = new BasicTenantInfo(tenantId, name); |
||||
|
return new DisposeAction(() => |
||||
|
{ |
||||
|
_currentTenantAccessor.Current = parentScope; |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
namespace Volo.Abp.MultiTenancy |
||||
|
{ |
||||
|
/* A null Current indicates that we haven't set it explicitly. |
||||
|
* A null Current.TenantId indicates that we have set null tenant id value explicitly. |
||||
|
* A non-null Current.TenantId indicates that we have set a tenant id value explicitly. |
||||
|
*/ |
||||
|
|
||||
|
public interface ICurrentTenantAccessor |
||||
|
{ |
||||
|
BasicTenantInfo Current { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Volo.Abp.MultiTenancy |
||||
|
{ |
||||
|
[Flags] |
||||
|
public enum MultiTenancyDatabaseStyle |
||||
|
{ |
||||
|
Shared = 1, |
||||
|
PerTenant = 2, |
||||
|
Hybrid = Shared | PerTenant |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
namespace Volo.Abp.MultiTenancy |
||||
|
{ |
||||
|
public class MultiTenancyOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// A central point to enable/disable multi-tenancy.
|
||||
|
/// Default: false.
|
||||
|
/// </summary>
|
||||
|
public bool IsEnabled { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Database style for tenants.
|
||||
|
/// Default: <see cref="MultiTenancyDatabaseStyle.Hybrid"/>.
|
||||
|
/// </summary>
|
||||
|
public MultiTenancyDatabaseStyle DatabaseStyle { get; set; } = MultiTenancyDatabaseStyle.Hybrid; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
namespace Volo.Abp.MultiTenancy |
||||
|
{ |
||||
|
public abstract class TenantResolveContributorBase : ITenantResolveContributor |
||||
|
{ |
||||
|
public abstract string Name { get; } |
||||
|
|
||||
|
//TODO: We can make this async
|
||||
|
public abstract void Resolve(ITenantResolveContext context); |
||||
|
} |
||||
|
} |
||||