Activities of "EngincanV"

Thanks, we will try to implement this.
How the "AddOpenIdConnect" part should go in this case, where the configuration is defined dynamically? Here is a general example how that normally goes.

        context.Services.AddAuthentication() 
            .AddOpenIdConnect("ABP2AzureADScheme", "Logon with Azure AD", options => 
            { 
                 
                options.Authority = configuration["AzureAd:Instance"] + configuration["AzureAd:TenantId"] + "/v2.0/"; 
                options.ClientId = configuration["AzureAd:ClientId"]; 
                options.ResponseType = OpenIdConnectResponseType.CodeIdToken; 
                options.CallbackPath = configuration["AzureAd:CallbackPath"]; 
                options.ClientSecret = configuration["AzureAd:ClientSecret"]; 
                options.RequireHttpsMetadata = false; 
                options.SaveTokens = true; 
                options.GetClaimsFromUserInfoEndpoint = true; 
                options.Scope.Add("email"); 
 
                options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub"); 
 
            }); 

Hi, actually this part can stay exactly like this. Since you will implement the IConfigureOptions<> it will update the configuration dynamically.

It didn't work. I believe the issue is not related to the EF type, as the migration already has the correct SQL type. It seems to be caused by a deserialization or mapping step in the pipeline. Since extra property values are stored as Object, it could be a boxing issue. When replacing IdentityUserAppService, I noticed that the BirthDate property is stored as DateTime instead of DateOnly. Additionally, the response includes a time part, which suggests unintended type conversion. Request:

{ 
 "extraProperties": { 
   "NationalIdentityNumber": "12345678901", 
   "BirthDate": "2001-03-27" <--- no time 
 }, 
 "userName": "test", 
 "name": "test", 
 "surname": "test", 
 "password": "test123", 
 "email": "test@test", 
 "phoneNumber": "", 
 "isActive": true, 
 "lockoutEnabled": true, 
 "emailConfirmed": false, 
 "phoneNumberConfirmed": false, 
 "shouldChangePasswordOnNextLogin": false, 
 "roleNames": [], 
 "organizationUnitIds": [] 
} 

Response:

{ 
    "tenantId": null, 
    "userName": "test", 
    "email": "test@test", 
    "name": "test", 
    "surname": "test", 
    "emailConfirmed": false, 
    "phoneNumber": "", 
    "phoneNumberConfirmed": false, 
    "supportTwoFactor": false, 
    "twoFactorEnabled": false, 
    "isActive": true, 
    "lockoutEnabled": true, 
    "isLockedOut": false, 
    "lockoutEnd": null, 
    "shouldChangePasswordOnNextLogin": false, 
    "concurrencyStamp": "6d0c02c007114cfa8a6eb816bd9f277c", 
    "roleNames": null, 
    "accessFailedCount": 0, 
    "lastPasswordChangeTime": "2025-03-27T07:16:52.8335677+00:00", 
    "isExternal": false, 
    "isDeleted": false, 
    "deleterId": null, 
    "deletionTime": null, 
    "lastModificationTime": null, 
    "lastModifierId": null, 
    "creationTime": "2025-03-27T09:16:52.8579278+02:00", 
    "creatorId": "519c6b51-3e6f-8cb8-cade-3a17de0dafb7", 
    "id": "51e457a2-1a1e-5930-a2d4-3a18e8a2d980", 
    "extraProperties": { 
        "BirthDate": "2001-03-27T00:00:00", <---- time added 
        "NationalIdentityNumber": "12345678901" 
    } 
} 

This is how I see the problem:

  1. Incorrect type casting in the pipeline.
  2. The date is not saved in the database due to the wrong type, but no error is thrown; instead, a default value is set.
  3. The response includes the user’s input, but time is added to date.

Btw, on UI date input is a simple text input, not a date picker.

Okay. I've reproduced the problem and it's exactly how you described it. I've created an issue for that: https://github.com/abpframework/abp/issues/22472

I'm refunding your ticket. Thanks for reporting the problem and your prompt explanation.

Regards.

Hi, EngincanV I did build. See my Step 4. Graph build. OK I can not add new Entity to [DemoSolution2] using ABP Suite. After adding it gives me error and I can not build using graph due to errors in module. So you did not answer my quition: why my module [NewModule] is impacted if I am adding entity into [DemoSolution2]?

I will share solution soon.

Thanks. I got your email, and will check the problem.

Answer

Hi, your finding that removing await allows the event to be published immediately when onUnitOfWorkComplete is false seems to be the correct workaround. Because, since you are not using the await keyword, it works like a sync method. But just notice, when you do that in an unhandled exception occurs then you are on your own in exception handling.

Second issue, after event published, even event handler method is virtual still it is not working correctly without using _unitOfWorkManager as below. Without using _unitOfWorkManager, child entities is not commiting to the db.

By using _unitOfWorkManager.Begin(requiresNew: true, isTransactional: true), you are explicitly creating a new and independent unit of work for the duration of your event handler's execution. This ensures that all operations within this using block, including changes to child entities, are tracked by EF Core within this specific unit of work.

Actually, since you are using the [UnitOfWork] attribute, normally it should work seamlessly but it's possible that the event handler is being invoked in a context where the ambient unit of work has already been completed or disposed, or perhaps the event handling mechanism in this specific scenario doesn't correctly hook into the ambient unit of work management for child entities. I'll check this.

Regards.

Hi, in the GetWaitingEventsAsync method, a new optional parameter was added, and your problem seems to related to package version inconsistency. I know you've already cleaned your solution, rebuilt it, and checked the .dll files, but it looks like one of your .csproj files may have an incorrect package version.

Could you please check your .csproj files and confirm that all package versions are correct?

Hi, thanks for the detailed information. Your code for extending the IdentityUser with a DateOnly property for BirthDate looks correct at first glance. ABP's entity extension mechanism should handle this. However, the fact that the property always defaults to 0001-01-01 suggests that the value is indeed not being set during user creation.

Your suspicion about type casting is likely on the right track. You can try to explicitly configure column type in mapping as follows:

ObjectExtensionManager.Instance.MapEfCoreProperty<IdentityUser, DateOnly>(
                UserExtraPropertyNames.BirthDate,
                (_, propertyBuilder) =>
                {
                    propertyBuilder.IsRequired();
                    propertyBuilder.HasColumnType("date");
                });

By explicitly setting the column type (propertyBuilder.HasColumnType("date")), I think your problem should be resolved. Can you try it and let me know if it fixes, please?

Hi, it seems in your App.razor file (under Components folder), you should set base as follows (update the related base tag's href):

<base href="/Session/Onboarding/" /> 

The <base> HTML element specifies the base URL to use for all relative URLs in a document. There can be only one <base> element in a document. Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base

Thanks for the answer! We are aiming to enable Microsoft Entra Id SSO login for some of our customers, that use our (Abp) application. How is that going to work when we cannot configure the azure tenant in the tenant's external login provider configuration? If only the client id and the secret are configurable, how can the customer point the configuration to their Microsoft Entra Id?

You're right to point out that simply configuring Client ID and Secret might not be enough for Microsoft Entra ID (now known as Microsoft Entra ID, formerly Azure AD) in a multi-tenant scenario. Each customer using their own Microsoft Entra ID will indeed have a unique Tenant ID (also known as Directory ID) that your application needs to target for authentication.

For that purpose, you should implement dynamic configuration using ICoonfigureOptions<>. Here is what you can do:

  1. Implement IConfigureOptions<OpenIdConnectOptions> for Azure AD:

Since Microsoft Entra ID uses the OpenID Connect protocol, you'll need to implement IConfigureOptions<OpenIdConnectOptions>.

public class AzureAdTenantOptionsProvider : IConfigureOptions<OpenIdConnectOptions>, ITransientDependency
{
    private readonly ICurrentTenant _currentTenant;
    private readonly ITenantAzureAdSettingsService _tenantAzureAdSettingsService; //NOTE: you need to implement this service

    public AzureAdTenantOptionsProvider(ICurrentTenant currentTenant, ITenantAzureAdSettingsService tenantAzureAdSettingsService)
    {
        _currentTenant = currentTenant;
        _tenantAzureAdSettingsService = tenantAzureAdSettingsService;
    }

    public void Configure(OpenIdConnectOptions options)
    {
        if (_currentTenant.Id.HasValue)
        {
            var tenantId = _currentTenant.Id.Value;
            var azureAdSettings = _tenantAzureAdSettingsService.GetAzureAdSettings(tenantId); // Implement this

            if (azureAdSettings != null && azureAdSettings.IsEnabled)
            {
                options.ClientId = azureAdSettings.ClientId;
                options.ClientSecret = azureAdSettings.ClientSecret;
                options.Authority = $"https://login.microsoftonline.com/{azureAdSettings.TenantId}/v2.0";
                options.ResponseType = "code"; // Or your preferred response type
                options.SaveTokens = true;
                options.GetClaimsFromUserInfoEndpoint = true;
                // Add other necessary options as per your requirements
            }
            else
            {
                // Or configure default behavior
            }
        }
        else
        {
            // Configure default Azure AD settings for the host if needed
        }
    }
}
  1. Register the option dynamically in your module class (inside ConfigureServices method):
services.ConfigureOptions<AzureAdTenantOptionsProvider>();

Note that, this requires additional implementation on your side and in the above example, I have just tried to provide you an approach. Since this is not fully related to ABP, you may need to check additional articles on the web.


In summary, to enable Microsoft Entra ID SSO login for different tenants:

  • You need to implement a dynamic option as I have described above, then create a service that returns tenantId from each customer for their Azure AD.
  • Our current system, only allows single-tenant configuration for EntraID, but by providing dynamic options, you can implement according to your business.

Regards.

Hi, I'm unable to reproduce your problem. Tried applying each step you provided but could not reproduce it. Your problems seem to be related to that you have not built your solution.

Please get a graphBuild on your solution (dotnet build /graphbuild) and see if really there is any build error. If still there are build errors, share your solution via email (to support@abp.io with ticket number), so I can check your solution. Since, could not produce the problem with the specified steps, I'm unable to help you at that point and waiting for your confirmation.

Regards.

Answer

Hi, yes you can generate CRUD pages via ABP CLI, please refer to the Generating CRUD Pages via Command Line section in the ABP Suite documentation.

Let me know, if you need further info. Regards.

I saw that reference but it assumes a json metadata file was generated via the Suite tool initially. If one is to manually replicate this, what are the minimum required properties?

You can use the following json template as an example:

Promotion.json (entity name: "Promotion", and only has a single property named "Title")

{
  "Id": "1148f4fe-8dfd-4435-8953-99db98b0c82a",
  "Name": "Promotion",
  "OriginalName": "Promotion",
  "NamePlural": "Promotions",
  "DatabaseTableName": "Promotions",
  "Namespace": "Promotions",
  "Type": 1,
  "MasterEntityName": null,
  "MasterEntity": null,
  "BaseClass": "FullAuditedAggregateRoot",
  "PageTitle": "Promotions",
  "MenuIcon": "file-alt",
  "PrimaryKeyType": "Guid",
  "PreserveCustomCode": true,
  "IsMultiTenant": false,
  "CheckConcurrency": true,
  "BulkDeleteEnabled": true,
  "ShouldCreateUserInterface": true,
  "ShouldCreateBackend": true,
  "ShouldExportExcel": true,
  "ShouldAddMigration": true,
  "ShouldUpdateDatabase": true,
  "CreateTests": true,
  "Properties": [
    {
      "Id": "e7c6fe43-3e99-495c-bde8-ecbbcd6793e3",
      "Name": "Title",
      "Type": "string",
      "EnumType": "",
      "EnumNamespace": "",
      "EnumAngularImport": "shared/enums",
      "EnumFilePath": null,
      "DefaultValue": null,
      "IsNullable": false,
      "IsRequired": false,
      "IsFilterable": true,
      "AllowEmptyStrings": false,
      "IsTextArea": false,
      "MinLength": null,
      "MaxLength": null,
      "SortOrder": 0,
      "SortType": 0,
      "Regex": "",
      "EmailValidation": false,
      "ShowOnList": true,
      "ShowOnCreateModal": true,
      "ShowOnEditModal": true,
      "ReadonlyOnEditModal": false,
      "EnumValues": null,
      "IsSelected": true,
      "MaxFileSize": null,
      "OrdinalIndex": 0
    }
  ],
  "NavigationProperties": [],
  "NavigationConnections": [],
  "ChildEntities": [],
  "PhysicalFileName": "Promotion.json"
}
Showing 341 to 350 of 1371 entries
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.1.0-preview. Updated on November 04, 2025, 06:41