Activities of "jkrause"

If you're creating a bug/problem report, please include followings:

  • ABP Framework version: v5.0.0.beta.3
  • UI type: MVC
  • DB provider: EF Core
  • Tiered (MVC) or Identity Server Separated (Angular): Tiered MVC
  • Exception message and stack trace: [10:22:37 ERR] Connection id "0HMD07N4O5EKN", Request id "0HMD07N4O5EKN:00000003": An unhandled exception was thrown by the application. Volo.Abp.AbpException: Could not find the bundle file '/libs/datatables.net-bs5/css/dataTables.bootstrap5.css' for the bundle 'Lepton.Global'!
  • Steps to reproduce the issue:" Update abp suite using: abp suite update --preview then proceed to start it using abp suite once the browser is opened error 500 is displayed and the exception listed above is visible in the console window that launched ABP Suite.

I have also updated the npm module of datatables.net-bs5 globally, but I presume the application is expecting this lib to be available from a local perspective.

If there is a more suited place to report v5 specific issues that would be great as the current support forum is less than accomodation filtering out the v5 specific issues.

PS. If this is a known issue, I'd like to request a credit refund of our question limit, tyvm :)


Update: I was able to add the library to the presumable path it is looking for: %USERPROFILE%\.dotnet\tools\.store\volo.abp.suite\5.0.0-beta.3\volo.abp.suite\5.0.0-beta.3\tools\net6.0\any\wwwroot\libs\ and added the bs5 package there copied from the npm folder, but it now refers to another missing library: Could not find the bundle file '/libs/bootstrap/js/bootstrap.enable.tooltips.everywhere.js' for the bundle 'Lepton.Global'!


Update 2: I've add an empty file called bootstrap.enable.tooltips.everywhere.js in that folder and abp suite now at least launches, seemingly without any console errors. So we can continue our day, hope this information can contribute to locating the issue, or perhaps if I missed something when upgrading.

Thanks a lot for your time and efforts!

Thanks alot for the quick reply and action, looking forward :)

The entire ABP Suite cannot handle file scoped namespaces, so that is a bigger issue. With the move to .NET 6 for ABP v5 it would be essential to have this working, e.g. after refactoring your code Suite does not understand it at all.

  • So file (re)generation fails, as it cannot find the region where to place the (updated) code
  • Loading files leaves the namespace blank as it cannot parse it

As an added feedback on the enums, if the enum is not of type int, everything else fails, this also applies to flagged enums. I am not sure if ABP Suite CRUD generator supports flags enums but I had to refactor it to explicit int values to work.

In addition, the enum values are added to JSON as: Enum:EnumType:EnumValue but if you look at https://docs.abp.io/en/abp/5.0/UI/AspNetCore/Tag-Helpers/Form-elements#label-localization-1 it says it looks in the localization texts with EnumType.EnumValue so right now I have untranslated enums everywhere.

Note: It would be very helpful to have a separate v5 issues thread like exists for v4.4 Note: If these issues should be posted on Github instead let us know :)

Thank you @albert!

I would like to create a computed property for the purpose of using this in the navigation dropdown as the display property. I think this scenario could be pretty common, especially with the desire to often add the IdentityUser as a navigation property to one of your own entities. So for that purpose I wanted to add a "FullName" property to IdentityUserDto, instead of now only being able to choose between Name and Surname that is available on this type (other properties are of course available, but not relevant to the question).

Having already been successful in adding multiple 'regular' properties to the IdentityUser entity and the IdentityUserDto class, I am thus familiar with the various options, and also what is currently available as information from the documentation. However, how could we achieve this type of extension property? I am unable to figure out how to access the Dto instance itself when providing the configuration, so I am confused how I can create my property.

As a reference, this is how I would declare the property if the Dto class was my own:

public string FullName => string.Join(" ", string.Empty, Name, Surname).Trim();

or as a common variation using interpolation:

public string FullName => $"{Name} {Surname}).Trim();

What do I need to provide to the configuration parameter to achieve the equivalent result?

user.AddOrUpdateProperty<string>(UserConsts.FullName, options => options.DefaultValue = <What should I use here?>);

I also looked at the DefaultValueFactory but this Func does not provide access to the referencing type.

Note: If this is currently not possible through the regular extension methods, is there any alternative or other work-around to achieve something similar?

Thanks for your time and effort.

Answer

ABP: v5.0.0-rc.1 CLI: v5.0.0-rc.1 Suite: v5.0.0-rc.1


When (re)generating the the various files using the CRUD generator the <MyModule>EntityFrameworkCoreModule class will:

  • Have the namespaces repeated on the top, e.g.:
using Acme.Abp.Crm.Contacts;
using Acme.Abp.Crm.Companies;
using Acme.Abp.Crm.Companies;
using Acme.Abp.Crm.Companies;
using Acme.Abp.Crm.Contacts;
using Acme.Abp.Crm.Companies;
  • Have the AddRepository mapping repeated inside ConfigureServices, e.g.:
options.AddRepository<Company, Companies.EfCoreCompanyRepository>();
options.AddRepository<Contact, Contacts.EfCoreContactRepository>();
options.AddRepository<Company, Companies.EfCoreCompanyRepository>();
options.AddRepository<Company, Companies.EfCoreCompanyRepository>();
options.AddRepository<Company, Companies.EfCoreCompanyRepository>();
options.AddRepository<Contact, Contacts.EfCoreContactRepository>();

Edit: This will happen after an Entity is updated (more properties, extra navigations) and then Save and generate is clicked, or when you simply click it twice. When you then switch to another entity that gets updated or otherwise and click Save and generate then this one will be repeated as well.

Answer

@albert,

The same thing happens for the <MyModule>ApplicationAutoMapperProfile class. I am regenerating the code, since I made changes to the templates, and the mapping code is then duplicated.

Situation after first code generation:

CreateMap<OrganizationUnit, LookupDto<Guid>>().ForMember(dest => dest.DisplayName, opt => opt.MapFrom(src => src.DisplayName));
CreateMap<IdentityUser, LookupDto<Guid?>>().ForMember(dest => dest.DisplayName, opt => opt.MapFrom(src => src.Email));

Method contents after second code generation:

CreateMap<OrganizationUnit, LookupDto<Guid>>().ForMember(dest => dest.DisplayName, opt => opt.MapFrom(src => src.DisplayName));
CreateMap<IdentityUser, LookupDto<Guid?>>().ForMember(dest => dest.DisplayName, opt => opt.MapFrom(src => src.Email));

// This is duplicated
CreateMap<IdentityUser, LookupDto<Guid>>().ForMember(dest => dest.DisplayName, opt => opt.MapFrom(src => src.Email));

As you can observe, it seems that the CRUD generator does not "see" that this mapping already exists? Similarly, this also happens with other mapping statements, where you would end up with multiple definitions when you regenerate the code for multiple entities in succession (still well within regular usage of the CRUD tooling I would say):

CreateMap<Company, CompanyDto>();
CreateMap<CompanyWithNavigationProperties, CompanyWithNavigationPropertiesDto>();

CreateMap<Company, LookupDto<Guid?>>().ForMember(dest => dest.DisplayName, opt => opt.MapFrom(src => src.Name));
// This is duplicated
CreateMap<Company, CompanyDto>();

Edit: The namespace duplication happens a lot as well for the *AppService classes and the classes *WithNavigationPropertiesDto.

(though I do realize this is minor and will not actually produce error situations)

Hope this explains it well enough, there are other parts where the Regex seems to be too greedy and will match too much code, even code the CRUD generator did not create, but I moved that code out to separate files to avoid it all to gether.

Thanks again for your time and effort.

Hi bolenton,

(Note: Making some assumptions here about the version of ABP that you are using, so I am going to assume v4.4 or v5.0 for the sake of my answer)

Disclaimer: Though my answer may work for your particular case, please don't view this as an official response from Support nor the proper and/or intended work-around to address this particular issue.

Whenever you create a new Tenant, there is an event handler class that is fired inside your project/module: AcmeTenantDatabaseMigrationHandler (where Acme is your project/module name).

Inside this class there is a method sequence that is being called whenever a new Tenant is created:

public async Task HandleEventAsync(TenantCreatedEto eventData)

This is called when you create a Tenant from inside the SaaS management screens. Inside it makes a call to the MigrateAndSeedForTenantAsync method that does a few things:

  1. Try to apply migrations to the target database in case the Tenant used a dedicated database
  2. Seed the default admin user to the newly created Tenant

There seems to be however a small issue, though perhaps unrelated, when [v] Use Shared Database is used, the code should not try to find an associated connection string for the newly created Tenant. When it cannot find the connection string, it tries to resolve an invalid database connection, which subsequently times out.

Though personally not sure how it fixes it, inside the MigrateAndSeedForTenantAsync method you can see the usage of the Unit of Work that is responsible for commiting your Tenant admin user:

// Seed data
using (var uow = _unitOfWorkManager.Begin(requiresNew: true, isTransactional: true))
{
    await _dataSeeder.SeedAsync(
        new DataSeedContext(tenantId)
            .WithProperty(IdentityDataSeedContributor.AdminEmailPropertyName, adminEmail)
            .WithProperty(IdentityDataSeedContributor.AdminPasswordPropertyName, adminPassword)
    );

    await uow.CompleteAsync();
}

Here you can see that requiresNew: true is being used, which if I understand it correctly, launches a new Unit of Work, which then cannot resolve the connection string of the current Tenant, and failes (due to time out). By settings this value to false, the "current" connection string of the existing Unit of Work is used, and thus the admin user is created as expected.

using (var uow = _unitOfWorkManager.Begin(requiresNew: false, isTransactional: true))

Hope this helps.

Hi,

In our application we have the following structure: Main Application -> Our Custom Module -> ABP Module(s). Inside this custom module, we want to be able to link our data with a User and/or Organisation. So through ABP Suite we have setup the needed navigation properties:

This took some effort, and if we didn't have the Commercial source package, I am not sure how to have accomplished this properly from inside a module, but that is besides the issue I am raising. Looking at the provided sample code generated within the AcmeDbContext class, we can see that [ReplaceDbContext(typeof(IIdentityProDbContext))] is used to replace that implementation with that of our own AcmeDbContext class.

Assuming this is how it is done, we did the same in our ModuleDbContext class and added the [ReplaceDbContext(typeof(IIdentityProDbContext))] attribute. We added the required DbSet properties and made sure that builder.ConfigureIdentityPro(); is called. We then added [ReplaceDbContext(typeof(IAcmeDbContext))] attribute to the main application AcmeDbContext so we can also expose our new entities there for later use (a call to builder.ConfigureAcme(); was also added as we replicated that setup).

We have a Company entity that we wish to connect to a User and Organization and using the Suite configuration above it generates the following class:

public partial class Company : FullAuditedEntity<Guid>, IMultiTenant
{
    public Guid OrganizationId { get; set; }    // OrganizationUnit is referenced here as expected
    public Guid UserId { get; set; }            // IdentityUser is referenced here as expected
    public Guid? TenantId { get; set; }

    /* Snip! */
}

Subsequently, the WithNavigationProperties class is generated as expected (putting it here for reference and completeness sake):

public class CompanyWithNavigationProperties
{
    public Company Company { get; set; }
    public OrganizationUnit OrganizationUnit { get; set; }
    public IdentityUser IdentityUser { get; set; }
}

And the necessary methods within the EfCoreCompanyRepository class is also available:

protected virtual async Task<IQueryable<CompanyWithNavigationProperties>> GetQueryForNavigationPropertiesAsync()
{
    return from company in (await GetDbSetAsync())
           join organizationUnit in (await GetDbContextAsync()).OrganizationUnits
               on company.OrganizationId equals organizationUnit.Id into organizationUnits
           from organizationUnit in organizationUnits.DefaultIfEmpty()
           join identityUser in (await GetDbContextAsync()).Users
               on company.UserId equals identityUser.Id into users
           from identityUser in users.DefaultIfEmpty()

           select new CompanyWithNavigationProperties
           {
               Company = company,
               OrganizationUnit = organizationUnit,
               IdentityUser = identityUser
           };
}

We build and start the application and navigate to the 'Companies' menu item; here we are treated with the following:

  1. Error 500 dialog in our Application
  2. Error Value cannot be null. (Parameter 'inner') message inside the EF Core log

After a slight refactoring of this particular method (as I wanted to get more familiar with the generated code and why it looked like that) it looks like the following:

protected virtual async Task<IQueryable<CompanyWithNavigationProperties>> GetQueryForNavigationPropertiesAsync()
{
    // Retrieve the current DbContext once (does it make a difference?)
    var dbContext = await GetDbContextAsync();

    // Add explicit references to the DbSet properties so we can easily inspect them during Debug
    var companies = dbContext.Companies;                // This is (await GetDbSetAsync()) in generated code
    var organizations = dbContext.OrganizationUnits;    // This DbSet<T> is null!
    var identityUsers = dbContext.Users;                // This DbSet<T> is null!

    return from company in companies

           // The generated join is an 'Outer Join' even though our navigations are 'Required', but irrelevant to the issue
           join organizationUnit in organizations
               on company.OrganizationId equals organizationUnit.Id into organizationUnits
           from organizationUnit in organizationUnits.DefaultIfEmpty()

           // The generated join is an 'Outer Join' even though our navigations are 'Required', but irrelevant to the issue
           join identityUser in identityUsers
               on company.UserId equals identityUser.Id into users
           from identityUser in users.DefaultIfEmpty()

           select new CompanyWithNavigationProperties
           {
               Company = company,
               OrganizationUnit = organizationUnit,
               IdentityUser = identityUser
           };
}

Now I could see that the DbSet properties that we took from the IIdentityProDbContext interface are seemingly not properly referring, or mapped to the table on this particular context, even though by my current, and still very much growing understanding of ABP, was configured correctly using [ReplaceDbContext(typeof(IIdentityProDbContext))]?

TL;DR, after using ReplaceDbContext the implemented DbSet properties are null inside a module that the main application is using.

What am I missing here? Why is Users and Organization null? Did I miss some key configuration part to make this work? Did I totally misunderstand the intention here? All the wiring of this logic is happening inside the Module and not the Application. We will make and use different pages and logic there that does not belong to this module. I have also checked the various sources of the Commercial modules, but it seems nothing is making a reference to either a User or Organization.

To summarize:

  1. Module has its own DbContext and entities, but Migration is handled in the Application
  2. Entities have either a required or optional link to an Organization and/or a User
  3. ReplaceDbContext was used inside the Module (IdentityPro) and also the Application (Module, IdentityPro) but the 'inherited' DbSet properties are null
  4. DependsOn was used inside the Module (IdentityPro) and also the Application (IdentityPro and Module)
  5. The AppService, Repository and Pages for the entities (Company) are inside the Module
  6. Additional properties have been added to IdentityUser and OrganizationUnit (that are migrated, displayed and working properly)

Not sure how to create a "simple" project that still reflects the setup and intent that we want to use within our Application. But since the project contains very little IP, I have no issue sharing the solution in its entirety.

Could this be transferred to you in a secure manner?

Showing 1 to 10 of 24 entries
Made with ❤️ on ABP v8.2.0-preview Updated on March 25, 2024, 15:11