and another question about localization.
As I mentioned earlier I have two hosting project. One is "Main" and the other is "ApproveIt" So when the angular request the localization from AbpApplicationLocalizationAppService. Sometimes it creates resource name: ApproveIt and sometimes it does not. Dont know the reason though. Because of that Localizations coming from ApproveIt is not translated in angular app.
So the question is How Abp is creating the resource if it is another microservice project? As i see it, Main project not including ApproveIt.Domain.Shared module. Should it be included or not? How does Abp fix the localization resources for different microservices? When i check the microservice project it is not also included. Is there any configuration needs to be done for it?
I have seen RemoteExternalLocalizationResource. But don't know how it works, if you can also explain that it would be helpful.
it is kind of interesting cause the localization values are in db.
Why it doesn't get the resource from the main app i wonder?
Hello again. Most of the things you have mentioned over here i did. But still having an error.
when i try to login with the new created tenant, it seems like it logs in but then angular page is redirecting to the error page. There is no error on console either. Also no error on network segment. And no error on the logs. In what conditions angular app is redirecting the user to the error page? can i intercept or debug that part in angular app?
this is the code that i use when new tenant is created.
public async Task HandleEventAsync(TenantCreatedEto eventData)
{
try
{
await MigrateDatabaseSchemaAsync(eventData.Id);
// await _identityServiceDataSeeder.SeedAsync(
// tenantId: eventData.Id,
// adminEmail: eventData.Properties.GetOrDefault(IdentityDataSeedContributor.AdminEmailPropertyName) ??
// DoohlinkConsts.AdminEmailDefaultValue,
// adminPassword:
// eventData.Properties.GetOrDefault(IdentityDataSeedContributor.AdminPasswordPropertyName) ??
// DoohlinkConsts.AdminPasswordDefaultValue
// );
// await _administrationServiceDataSeeder.SeedAsync(eventData.Id);
using (var uow = _unitOfWorkManager.Begin(requiresNew: true, isTransactional: true))
{
await _dataSeeder.SeedAsync(
new DataSeedContext(eventData.Id)
.WithProperty(IdentityDataSeedContributor.AdminEmailPropertyName,
DoohlinkConsts.AdminEmailDefaultValue)
.WithProperty(IdentityDataSeedContributor.AdminPasswordPropertyName,
DoohlinkConsts.AdminPasswordDefaultValue)
);
await uow.CompleteAsync();
}
await _dataSeeder.SeedAsync();
}
catch (Exception ex)
{
await HandleErrorTenantCreatedAsync(eventData, ex);
}
}
it creates new tenant but angular app is redirecting me to error page when i tried to login as admin.
AbpPermissionGrants table have the records for the tenant. Can you tell me the conditions when angular app redirection to the error page is triggered. or how can i intercept or override it?
Hello. Thank you for the feedback. I will try your suggestion.
I created an empty microservice project and changed the data seeder just like you did. When I create the new tenant, there isn't any problem; everything works as expected. Can you share the logs when you get the exception?
When i try with idataseeder, it also seems working at first look, but when you try to login to angular app with the admin user of the tenant, it is redirecting to error page.No errors on the server side. How can i find out what is going wrong on the angular side? my intuition is user doesn't have any permissions so it redirects you to error page. But i will do more thorough tests, and get back to you.
My Project was a monolith project. Since it is modular now i am trying to change one of the modules to microservice. Do you have any project that i can look into? or a guide how to convert your module to microservice? What should be the dependencies for the host project of the module. What should be the dependencies for domain and infrastructure layer?
I am also having some problems with localizations. I have 2 hosts that is running on my cluster. Let's calll them "Main Host" and "Side Host". Localizations are stored on Main Db. If I start the servers first time all the localizations are working fine. Then after some time if i restart the side server, all the "side" localizations are gone from UI. To fix that i need to flush the redis. Then everything is fixed again. What can be the reason for it? I removed DynamicLocalizationResourceContributor from both hosts. So i was suspicious about that, but even if revert it and try it again. It doesn't work. On application start if redis cache is empty, it gets it from db and hold it in the server memory i guess. The problem is related with redis i believe? and if i remove DynamicLocalizationResourceContributor does it mean it longer use Redis? or it doesn't write to redis or it doesn't read or write to redis?
Hello again, i don't think it is related with PermissionDefinitionManager. I did some tests. Actually Permissions are seeded in the db. But i am getting error on the ui side when i try to login here is the video for that. What i did is just to change dataseeding as i mentioned before.
https://drive.google.com/file/d/1cXbxgi6WZF1_m7c9d8iDbqd49lpCU1C2/view?usp=sharing
here is the code that i have changed, when the new tenant has been created it triggers.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using ApproveIt.Shared.Hosting.Microservices.DbMigrations;
using Doohlink.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Volo.Abp.Data;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Identity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Uow;
using Volo.Saas.Tenants;
namespace Doohlink.DbMigrations;
public class AdzupDatabaseMigrationEventHandler
: DatabaseMigrationEventHandlerBase<DoohlinkDbContext>,
IDistributedEventHandler<TenantCreatedEto>,
IDistributedEventHandler<TenantConnectionStringUpdatedEto>,
IDistributedEventHandler<ApplyDatabaseMigrationsEto>
{
private readonly IdentityServiceDataSeeder _identityServiceDataSeeder;
private readonly AdministrationServiceDataSeeder _administrationServiceDataSeeder;
private readonly IDataSeeder _dataSeeder;
public AdzupDatabaseMigrationEventHandler(
ILoggerFactory loggerFactory,
ICurrentTenant currentTenant,
IUnitOfWorkManager unitOfWorkManager,
ITenantStore tenantStore,
ITenantRepository tenantRepository,
IDistributedEventBus distributedEventBus,
IdentityServiceDataSeeder identityServiceDataSeeder,
AdministrationServiceDataSeeder administrationServiceDataSeeder,
IDataSeeder dataSeeder) : base(
loggerFactory,
currentTenant,
unitOfWorkManager,
tenantStore,
tenantRepository,
distributedEventBus,
DoohlinkDbProperties.ConnectionStringName)
{
_identityServiceDataSeeder = identityServiceDataSeeder;
_administrationServiceDataSeeder = administrationServiceDataSeeder;
_dataSeeder = dataSeeder;
}
public async Task HandleEventAsync(ApplyDatabaseMigrationsEto eventData)
{
if (eventData.DatabaseName != DatabaseName)
{
return;
}
try
{
var schemaMigrated = await MigrateDatabaseSchemaAsync(eventData.TenantId);
await _identityServiceDataSeeder.SeedAsync(
tenantId: eventData.TenantId,
adminEmail: DoohlinkConsts.AdminEmailDefaultValue,
adminPassword: DoohlinkConsts.AdminPasswordDefaultValue
);
await _administrationServiceDataSeeder.SeedAsync(eventData.TenantId);
if (eventData.TenantId == null && schemaMigrated)
{
/* Migrate tenant databases after host migration */
await QueueTenantMigrationsAsync();
}
}
catch (Exception ex)
{
await HandleErrorOnApplyDatabaseMigrationAsync(eventData, ex);
}
}
public async Task HandleEventAsync(TenantCreatedEto eventData)
{
try
{
await MigrateDatabaseSchemaAsync(eventData.Id);
await _dataSeeder.SeedAsync(
new DataSeedContext(eventData.Id)
.WithProperty(IdentityDataSeedContributor.AdminEmailPropertyName,
DoohlinkConsts.AdminEmailDefaultValue)
.WithProperty(IdentityDataSeedContributor.AdminPasswordPropertyName,
DoohlinkConsts.AdminPasswordDefaultValue)
);
// await _identityServiceDataSeeder.SeedAsync(
// tenantId: eventData.Id,
// adminEmail: eventData.Properties.GetOrDefault(IdentityDataSeedContributor.AdminEmailPropertyName) ??
// DoohlinkConsts.AdminEmailDefaultValue,
// adminPassword:
// eventData.Properties.GetOrDefault(IdentityDataSeedContributor.AdminPasswordPropertyName) ??
// DoohlinkConsts.AdminPasswordDefaultValue
// );
// await _administrationServiceDataSeeder.SeedAsync(eventData.Id);
}
catch (Exception ex)
{
await HandleErrorTenantCreatedAsync(eventData, ex);
}
}
public async Task HandleEventAsync(TenantConnectionStringUpdatedEto eventData)
{
if (eventData.ConnectionStringName != DatabaseName &&
eventData.ConnectionStringName != ConnectionStrings.DefaultConnectionStringName ||
eventData.NewValue.IsNullOrWhiteSpace())
{
return;
}
try
{
await MigrateDatabaseSchemaAsync(eventData.Id);
await _identityServiceDataSeeder.SeedAsync(
tenantId: eventData.Id,
adminEmail: DoohlinkConsts.AdminEmailDefaultValue,
adminPassword: DoohlinkConsts.AdminPasswordDefaultValue
);
await _administrationServiceDataSeeder.SeedAsync(eventData.Id);
/* You may want to move your data from the old database to the new database!
* It is up to you. If you don't make it, new database will be empty
* (and tenant's admin password is reset to IdentityServiceDbProperties.DefaultAdminPassword). */
}
catch (Exception ex)
{
await HandleErrorTenantConnectionStringUpdatedAsync(eventData, ex);
}
}
}
if i uncomment the lines that is commented(if i revert it like in microservice template), it works fine. What can be the reason i wonder? and i can see the permissions that is seeded in db for newly created tenant. (for idataseeder)
Hello @gterdem, Thank you for the answer, but i think we are talking different things. I will try to explain what i am thinking step by step, hope i can manage that. I will talk about AdministrationService inside microservice template and how do I think about it.
var multiTenancySide = CurrentTenant.GetMultiTenancySide();
var permissionNames = (await PermissionDefinitionManager.GetPermissionsAsync())
.Where(p => p.MultiTenancySide.HasFlag(multiTenancySide))
.Where(p => !p.Providers.Any() || p.Providers.Contains(RolePermissionValueProvider.ProviderName))
.Select(p => p.Name)
.ToArray();
await PermissionDataSeeder.SeedAsync(
RolePermissionValueProvider.ProviderName,
"admin",
permissionNames,
context?.TenantId
);
var multiTenancySide = tenantId == null
? MultiTenancySides.Host
: MultiTenancySides.Tenant;
var permissionNames = (await _permissionDefinitionManager
.GetPermissionsAsync())
.Where(p => p.MultiTenancySide.HasFlag(multiTenancySide))
.Where(p => !p.Providers.Any() || p.Providers.Contains(RolePermissionValueProvider.ProviderName))
.Select(p => p.Name)
.ToArray();
_logger.LogInformation($"Seeding admin permissions.");
await _permissionDataSeeder.SeedAsync(
RolePermissionValueProvider.ProviderName,
"admin",
permissionNames,
tenantId
);
what do i miss over here? can you point out? look at the image below, why this code won't work?
Since i am curious about the question above, I do not use microservice template, i have a different setup. In my setup all abp modules are in one microservice, So i don't have any separated abp modules in different microservices, I am expecting data seeding to seed default static permissions but IDataSeeder not seeding the permission data when i create new tenant. That's why i raised the question at the first place. But if you can answer the above i suppose it would help with my problem.
Hello @gterdem, That's exactly what i think, and that's why i raised the question,
If we continue from microservice example, for ex, in identityservice (Microservice) you have reference to identiy module, you indirectly referencing idataseedcontributor of the identity module. So why not call _dataseeder.Seed() method directly instead explicit calls to different dataseed service? I think you want to seed the data of the related microservice isn't it (which includes related modules)?
For ex, IdentityService (in Microservice Template) includes (Volo.Abp.Identity.Pro.Domain and Volo.Abp.OpenIddict.Pro.Domain) which also includes related DataSeedContributors (for ex, IdentitySeedContributor) coming from those modules. Am i missing sth over here?
here is the second part of my question
Let's assume that i create one microservice containing all abp modules,(which was my monolith app). Let's name this MainService. Then created extra microservice. Let's name it ExtraService. In this case if i call IDataSeeder.Seed() method when new tenant is created according to your explanation everything should work fine. And MainService should create admin user and permissions. But i am seeing that permissions are not created. What can be the reason for that?
ok thank you @maliming
Hello again, yes i have read that, but the question over here is
Why IDataSeeder is not directly triggered even on the fly migrations? Cause the microservice already includes those classes (classes that implements IDataSeedContributor)?
To examplify it. Why don't we directly trigger IDataSeeder like i have shown here, when the tenant is created.
Or another way of asking this is, why monolith app can seed the identity data with IDataSeeder and Identity MicroService can not with the same method? (so we need to use IIdentityDataSeeder explicitly)