أنشطة "cangunaydin"

إجابة

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.

  • PermissionManagement Module is part of administration service
  • So it includes PermissionDataSeedContributor that you have sent the link in the previous message.
  • When application started it registers the PermissionDataSeedContributor to the dependency injection system.
  • When you compare AdministrationServiceDataSeeder (omitting language management for now) and PermissionDataSeedContributor they are exactly doing same thing (correct me if i am wrong over here or i miss sth)

PermissionDataSeedContributor


  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
        );

AdministrationServiceDataSeeder

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
 );
  • So as a conclusion, if i inject IDataSeeder to AdministrationServiceDataSeeder and call _dataSeeder.SeedAsync(), i am expecting the _dataSeeder.SeedAsync() should trigger PermissionDataSeedContributor, and seeding the data.

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)

Ok i figured it out at the end. It was working fine on my local. My production was on azure kubernetes service with nginx ingress. So over there i need to do some configuration to accept underscores for tenant header.

https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#enable-underscores-in-headers

now it works fine.

Hello again, I realized now it only login with host account and not allowing tenant logins. When i give email address, there was already an email address on the host account with that email address and password was the same. That's why it logged in with tenant email address but not with username.

So the question here is why tenant header is ignored while i am doing the request? here is what i have found.

as you can see tenant header is over there. but i am always getting invalid_grant with invalid email address and password. any idea what can be the problem?

oh nice, thank you. I think there are also many problems with the profile page but I can create a new ticket for them. thanks for your help.

Thanks for the information. I have managed to fix it now. It works without autofac. here is the step by step what i did if somebody wants to do it.

1- Change all your dynamic client proxies to static ones. 2- Remove Autofac from the project solution

[DependsOn(
    typeof(AbpMauiClientModule),
    typeof(AbpHttpClientIdentityModelModule),
    typeof(DoohlinkHttpApiStaticClientModule), //your static client over here
    typeof(AbpAutoMapperModule)
)]
public class DoohlinkMauiModule : AbpModule
{

3- Remove Autofac from MauiProgram.cs it should be sth similar like this, I have created static methods to get the required service from dependency injection for the following steps.

 public static IAbpApplicationWithExternalServiceProvider? AbpApplication { get; private set; }

    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .UseMauiCommunityToolkit()
            .ConfigureSyncfusionCore()
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
                fonts.AddFont("Roboto-Regular.ttf", "Roboto");
                fonts.AddMaterialIconFonts();
            });

        builder.ConfigureMauiHandlers(handlers =>
        {
            handlers.AddHandler(typeof(Entry), typeof(EntryVisualEffectsRemoverHandler));
            handlers.AddHandler(typeof(Picker), typeof(PickerVisualEffectsRemoverHandler));
            handlers.AddHandler(typeof(DatePicker), typeof(DatePickerVisualEffectsRemoverHandler));
        });
        ConfigureConfiguration(builder);

        builder.Services.AddApplication<DoohlinkMauiModule>(options =>
        {
            options.Services.ReplaceConfiguration(builder.Configuration);
        });

#if DEBUG
        builder.Logging.AddDebug();
#endif
        var app = builder.Build();

        AbpApplication = app.Services.GetRequiredService<IAbpApplicationWithExternalServiceProvider>();
        AbpApplication.Initialize(app.Services);

        return app;
    }

    public static T? GetRequiredService<T>() where T : class
    {
        return AbpApplication?.Services.GetRequiredService<T>();
    }

    public static object? GetRequiredService(Type type)
    {
        return AbpApplication?.Services.GetRequiredService(type);
    }

4- ReplaceService for MauiCachedApplicationConfigurationClient. Add custom class

    [Volo.Abp.DependencyInjection.Dependency(ReplaceServices = true)]
[ExposeServices(typeof(ICachedApplicationConfigurationClient), typeof(MauiCachedApplicationConfigurationClient))]
public class DoohlinkMauiCachedApplicationConfigurationClient : MauiCachedApplicationConfigurationClient
{
    public DoohlinkMauiCachedApplicationConfigurationClient(
        AbpApplicationConfigurationClientProxy applicationConfigurationClientProxy,
        AbpApplicationLocalizationClientProxy applicationLocalizationClientProxy,
        ApplicationConfigurationCache cache,
        ICurrentTenantAccessor currentTenantAccessor) : base(applicationConfigurationClientProxy,
        applicationLocalizationClientProxy, cache, currentTenantAccessor)
    {
        ApplicationConfigurationClientProxy.LazyServiceProvider =
            MauiProgram.GetRequiredService<IAbpLazyServiceProvider>()!;
        ApplicationLocalizationClientProxy.LazyServiceProvider = MauiProgram.GetRequiredService<IAbpLazyServiceProvider>()!;
    }
}

5- Change ViewModelBase, so it doesn't need LazyServiceProvider anymore.

   public abstract partial class DoohlinkViewModelBase : ObservableObject
{

    public ICurrentTenant CurrentTenant { get; }
    
    public ICurrentUser CurrentUser { get; }

    public IAbpAuthorizationService AuthorizationService { get; }

    public LocalizationResourceManager L { get; }

    protected DoohlinkViewModelBase()
    {
        CurrentTenant = MauiProgram.GetRequiredService<ICurrentTenant>()!;
        CurrentUser = MauiProgram.GetRequiredService<ICurrentUser>()!;
        AuthorizationService = MauiProgram.GetRequiredService<IAbpAuthorizationService>()!;
        L = MauiProgram.GetRequiredService<LocalizationResourceManager>()!;
        
    }

    protected void HandleException(Exception exception, [CallerMemberName] string? methodName = null)
    {
        if (Application.Current is { MainPage: { } })
        {
            Application.Current.MainPage.DisplayAlert(L["Error"].Value, exception.Message, L["OK"].Value);
        }
    }
}

6- Change the MauiModule so lazyserviceprovider can be resolved when you call your app services.

 private void ConfigureServiceProviders(ServiceConfigurationContext context)
    {
        context.Services.Replace(ServiceDescriptor.Transient<IProfileAppService>(serviceProvider =>
        {
            var profileClientProxy = serviceProvider.GetRequiredService<ProfileClientProxy>();
            profileClientProxy.LazyServiceProvider = serviceProvider.GetRequiredService<IAbpLazyServiceProvider>();
            return profileClientProxy;
        }));

        context.Services.Replace(ServiceDescriptor.Transient<IIdentityUserAppService>(serviceProvider =>
        {
            var identityUserClientProxy = serviceProvider.GetRequiredService<IdentityUserClientProxy>();
            identityUserClientProxy.LazyServiceProvider = serviceProvider.GetRequiredService<IAbpLazyServiceProvider>();
            return identityUserClientProxy;
        }));
        context.Services.Replace(ServiceDescriptor.Transient<IAccountAppService>(serviceProvider =>
        {
            var accountClientProxy = serviceProvider.GetRequiredService<AccountClientProxy>();
            accountClientProxy.LazyServiceProvider = serviceProvider.GetRequiredService<IAbpLazyServiceProvider>();
            return accountClientProxy;
        }));

        context.Services.Replace(ServiceDescriptor.Transient<IScreenAppService>(serviceProvider =>
        {
            var screenClientProxy = serviceProvider.GetRequiredService<ScreenClientProxy>();
            screenClientProxy.LazyServiceProvider = serviceProvider.GetRequiredService<IAbpLazyServiceProvider>();
            return screenClientProxy;
        }));


        context.Services.Replace(ServiceDescriptor.Transient<ITenantAppService>(serviceProvider =>
        {
            var tenantClientProxy = serviceProvider.GetRequiredService<TenantClientProxy>();
            tenantClientProxy.LazyServiceProvider = serviceProvider.GetRequiredService<IAbpLazyServiceProvider>();
            return tenantClientProxy;
        }));
    }

you need to add whatever appservice you use over here. Maybe this can be automated for the future.

7- Add permissions manually.

    public override void PreConfigureServices(ServiceConfigurationContext context)
    {
        AddPermissions(context);
#if DEBUG
        PreConfigure<AbpHttpClientBuilderOptions>(options =>
        {
            options.ProxyClientBuildActions.Add((_, clientBuilder) =>
            {
                clientBuilder.ConfigurePrimaryHttpMessageHandler(GetInsecureHandler);
            });
        });
#endif
    }
    private void AddPermissions(ServiceConfigurationContext context)
    {
        Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
        var permissionDefinitionTypes = assemblies.SelectMany(o => o.GetTypes()).Where(o =>
                typeof(IPermissionDefinitionProvider).IsAssignableFrom(o) &&
                o.Name != nameof(IPermissionDefinitionProvider) && o.Name != nameof(PermissionDefinitionProvider))
            .Distinct().ToList();

        context.Services.Configure<AbpPermissionOptions>(options =>
        {
            options.DefinitionProviders.AddIfNotContains(permissionDefinitionTypes);
        });
    }

And voila... I am gonna close this issue liangshiwei but one last question. How can I get the source code of AbpMauiClientModule? thank you for the help.

thank you. I will do that.

عرض 11 الي 20 من 69 إدخالات
Made with ❤️ on ABP v8.2.0-preview Updated on مارس 25, 2024, 15:11