|
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", |
|||
"texts": { |
|||
"GivenTenantIsNotAvailable": "Given tenant is not available: {0}", |
|||
"SwitchTenant": "Switch tenant", |
|||
"Tenant": "Tenant", |
|||
"Switch": "switch", |
|||
"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); |
|||
} |
|||
} |
|||