- ABP Framework version: v7.3.0
- UI Type: Angular
- Database System: EF Core (SQL Server, Oracle, MySQL, PostgreSQL, etc..)
- Tiered (for MVC) or Auth Server Separated (for Angular): yes
-
- Exception message and full stack trace:
- Steps to reproduce the issue:
ABP multitenancy;
Can a tenant have multiple tenants in ABP Commercial? If not, what solution is the best practice for this use case? For example, Tenant-1 has 2 sub tenants. -Host -- Tenant-1 -------- Sub Tenant-01 --------Sub Tenant-02
How can I override the routing path for Auto API Controllers? I mean that all API endpoint in the swagger must be generated with tenant-id by default as follows;
GetAsync(Guid id) GET /api/app/{tenant-id}/book/{id} GetListAsync() GET /api/app/{tenant-id}/book CreateAsync(CreateBookDto input) POST /api/app/{tenant-id}/book UpdateAsync(Guid id, UpdateBookDto input) PUT /api/app/{tenant-id}/book/{id} DeleteAsync(Guid id) DELETE /api/app/{tenant-id}/book/{id} GetEditorsAsync(Guid id) GET /api/app/{tenant-id}/book/{id}/editors CreateEditorAsync(Guid id, BookEditorCreateDto input) POST /api/app/{tenant-id}/book/{id}/editor
7 Answer(s)
-
0
Can a tenant have multiple tenants in ABP Commercial?
No, ABP does not provide such an infrastructure, you need to implement it yourself.
If not, what solution is the best practice for this use case?
This is a big topic. I can only give you some simple ideas:
Use the extended entity system to add
ParentTenantId
property toTenant
entity: https://docs.abp.io/en/abp/latest/Customizing-Application-Modules-Extending-EntitiesData isolation between tenants, you can manually close the data filter: https://docs.abp.io/en/abp/latest/Data-Filtering#imultitenant
How can I override the routing path for Auto API Controllers? I mean that all API endpoint in the swagger must be generated with tenant-id by default as follows;
You can try:
[ExposeServices(typeof(IConventionalRouteBuilder))] public class MyConventionalRouteBuilder : ConventionalRouteBuilder { public MyConventionalRouteBuilder(IOptions<AbpConventionalControllerOptions> options) : base(options) { } protected override string NormalizeControllerNameCase(string controllerName, ConventionalControllerSetting? configuration) { var name = base.NormalizeControllerNameCase(controllerName, configuration); return name += "/{tenant-id}"; } } [ExposeServices(typeof(IAbpServiceConvention))] public class MyAppServiceConvention : AbpServiceConvention { public MyAppServiceConvention(IOptions<AbpAspNetCoreMvcOptions> options, IConventionalRouteBuilder conventionalRouteBuilder) : base(options, conventionalRouteBuilder) { } protected override void ConfigureSelector(ControllerModel controller, ConventionalControllerSetting? configuration) { RemoveEmptySelectors(controller.Selectors); var controllerType = controller.ControllerType.AsType(); var remoteServiceAtt = ReflectionHelper.GetSingleAttributeOrDefault<RemoteServiceAttribute>(controllerType.GetTypeInfo()); if (remoteServiceAtt != null && !remoteServiceAtt.IsEnabledFor(controllerType)) { return; } if (controller.Selectors.Any(selector => selector.AttributeRouteModel != null)) { foreach (var selector in controller.Selectors) { if (selector.AttributeRouteModel != null) { selector.AttributeRouteModel.Template += "/{tenant-id}"; } } return; } var rootPath = GetRootPathOrDefault(controller.ControllerType.AsType()); foreach (var action in controller.Actions) { ConfigureSelector(rootPath, controller.ControllerName, action, configuration); } } }
You need also to create a custom tenant resolver to get the resolve from route:
https://docs.abp.io/en/abp/latest/Multi-Tenancy#custom-tenant-resolvers
-
0
Thanks. That is good insight
-
0
ok
-
0
I have checked this documentation. However, I need help to implement the following use case. https://docs.abp.io/en/abp/latest/Data-Filtering?&_ga=2.210300374.2127151687.1690988301-1686284556.1681286165#entityframework-core
Could you provide a sample on how to implement complex use-case (not only filter with a boolean value)?
In my use case, I need to find all sub Tenants by ParentTenantId and then filter the data accordingly.
List<Guid> subTenants = new List<Guid>(){ SubTenantsGuids};
I need to use create an expression like tenantId=> subTenants.contains(tenantId)
protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>() { var expression = base.CreateFilterExpression<TEntity>(); if (typeof(IMultiTenant).IsAssignableFrom(typeof(TEntity))) { Expression<Func<TEntity, bool>> isTenantFilter = e => !IsTenantFilterEnabled || EF.Property<Guid>(e, "TenantId"); expression = expression == null ? isTenantFilter : CombineExpressions(expression, isTenantFilter); } return expression; }
How I can filter by a list of Guid in the section of Expression<Func<TEntity, bool>> isTenantFilter = e => !IsTenantFilterEnabled || EF.Property<Guid>(e, "TenantId"); expression = expression == null ? isTenantFilter : CombineExpressions(expression, isTenantFilter);**
Thanks in Advance
-
0
Hi,
It's easy to do.
For example:
var parentTenantId = currentTenant.Id.ToString(); using(currentTenant.Change(null)) { var tenants = await tenantRepository.GetListAsync(); var subTenants = tenants.where(x => x.GetProperty<string>("ParentTenantId") == parentTenantId).ToList(); }
-
0
Thanks for the prompt answer.
I would like to create a global filter instead of aligning each Domain Service class like as following.
namespace MultiTenancyDemo.Products { public class ProductManager : DomainService { private readonly IRepository<Product, Guid> _productRepository; public ProductManager(IRepository<Product, Guid> productRepository) { _productRepository = productRepository; } public async Task<long> GetProductCountAsync(Guid? tenantId) { using (CurrentTenant.Change(tenantId)) { return await _productRepository.GetCountAsync(); } } } }
My question is about how to implement globally
Do you think the following code may work?
protected override Expression> CreateFilterExpression() { var parentTenantId = currentTenant.Id.ToString(); Guid[] tenantIdsToFilter; using(currentTenant.Change(null)) { var tenants = await tenantRepository.GetListAsync(); var subTenants = tenants.where(x => x.GetProperty<string>("ParentTenantId") == parentTenantId).ToList(); tenantIdsToFilter = subTenants.Select(t=>t.Id).ToArray(); } Expression<Func<TEntity, bool>> tenantIdFilterExpression = e => !IsActiveFilterEnabled || tenantIdsToFilter.Contains(EF.Property<Guid>(e, "TenantId")); ....... }
-
0
Do you think the following code may work?
I think it's no problem, you can give it a try.