diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json index e48a677ee75..2fc0859b589 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json @@ -685,6 +685,7 @@ "RedisManagement": "Redis Management", "Permission:RedisManagement": "Redis Management", "UserCleanUp": "User Clean Up", - "Permission:UserCleanUp": "User Clean Up" + "Permission:UserCleanUp": "User Clean Up", + "AllowPrivateQuestion": "Allow Private Question" } } diff --git a/docs/en/Community-Articles/2024-12-01-OpenAI-Integration/POST.md b/docs/en/Community-Articles/2024-12-01-OpenAI-Integration/POST.md index c07509d2e3a..4706e79f49f 100644 --- a/docs/en/Community-Articles/2024-12-01-OpenAI-Integration/POST.md +++ b/docs/en/Community-Articles/2024-12-01-OpenAI-Integration/POST.md @@ -62,6 +62,8 @@ dotnet add package Microsoft.Extensions.AI.OpenAI --prerelease > Replace the value of the `Key` with your OpenAI API key. +> **Important Security Note**: Storing sensitive information like API keys in `appsettings.json` is not recommended due to security concerns. Please refer to the [official Microsoft documentation](https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets) for secure secret management best practices. + Next, add the following code to the `ConfigureServices` method in `OpenAIIntegrationBlazorModule`: ```csharp diff --git a/docs/en/Community-Articles/2025-01-24-Understanding-Transactions-in-ABP-Unit-Of-Work/POST.md b/docs/en/Community-Articles/2025-01-24-Understanding-Transactions-in-ABP-Unit-Of-Work/POST.md new file mode 100644 index 00000000000..49a75b2a25f --- /dev/null +++ b/docs/en/Community-Articles/2025-01-24-Understanding-Transactions-in-ABP-Unit-Of-Work/POST.md @@ -0,0 +1,327 @@ +# Understanding Transactions in ABP Unit of Work + +[The Unit of Work](https://en.wikipedia.org/wiki/Unit_of_work) is a software design pattern that maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems to ensure that all changes are made within a single transaction. + +![pic](./pic.png) + +## Transaction Management Overview + +One of the primary responsibilities of the Unit of Work is managing database transactions. It provides the following transaction management features: + +- Automatically manages database connections and transaction scopes, eliminating the need for manual transaction control +- Ensures business operation integrity by making all database operations within a unit of work either succeed or roll back completely +- Supports configuration of transaction isolation levels and timeout periods +- Supports nested transactions and transaction propagation + +## Transaction Behavior + +### Default Transaction Settings + +You can modify the default behavior through the following configuration: + +```csharp +Configure(options => +{ + /* + Modify the default transaction behavior for all unit of work: + - UnitOfWorkTransactionBehavior.Enabled: Always enable transactions, all requests will start a transaction + - UnitOfWorkTransactionBehavior.Disabled: Always disable transactions, no requests will start a transaction + - UnitOfWorkTransactionBehavior.Auto: Automatically decide whether to start a transaction based on HTTP request type + */ + options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled; + + // Set default timeout + options.Timeout = TimeSpan.FromSeconds(30); + + // Set default isolation level + options.IsolationLevel = IsolationLevel.ReadCommitted; +}); +``` + +### Automatic Transaction Management + +ABP Framework implements automatic management of Unit of Work and transactions through middlewares, MVC global filters, and interceptors. In most cases, you don't need to manage them manually + +### Transaction Behavior for HTTP Requests + +By default, the framework adopts an intelligent transaction management strategy for HTTP requests: +- `GET` requests won't start a transactional unit of work because there is no data modification +- Other HTTP requests (`POST/PUT/DELETE` etc.) will start a transactional unit of work + +### Manual Transaction Control + +If you need to manually start a new unit of work, you can customize whether to start a transaction and set the transaction isolation level and timeout: + +```csharp +// Start a transactional unit of work +using (var uow = _unitOfWorkManager.Begin( + isTransactional: true, + isolationLevel: IsolationLevel.RepeatableRead, + timeout: 30 +)) +{ + // Execute database operations within transaction + await uow.CompleteAsync(); +} +``` + +```csharp +// Start a non-transactional unit of work +using (var uow = _unitOfWorkManager.Begin( + isTransactional: false +)) +{ + // Execute database operations without transaction + await uow.CompleteAsync(); +} +``` + +### Configuring Transactions Using `[UnitOfWork]` Attribute + +You can customize transaction behavior by using the `UnitOfWorkAttribute` on methods, classes, or interfaces: + +```csharp +[UnitOfWork( + IsTransactional = true, + IsolationLevel = IsolationLevel.RepeatableRead, + Timeout = 30 +)] +public virtual async Task ProcessOrderAsync(int orderId) +{ + // Execute database operations within transaction +} +``` + +### Non-Transactional Unit of Work + +In some scenarios, you might not need transaction support. You can create a non-transactional unit of work by setting `IsTransactional = false`: + +```csharp +public virtual async Task ImportDataAsync(List items) +{ + using (var uow = _unitOfWorkManager.Begin( + isTransactional: false + )) + { + foreach (var item in items) + { + await _repository.InsertAsync(item, autoSave: true); + // Each InsertAsync will save to database immediately + // If subsequent operations fail, saved data won't be rolled back + } + + await uow.CompleteAsync(); + } +} +``` + +Applicable scenarios: +- Batch import data scenarios where partial success is accepted +- Read-only operations, such as queries +- Scenarios with low data consistency requirements + +### Methods to Commit Transactions + +#### In Transactional Unit of Work + +A Unit of Work provides several methods to commit changes to the database: + +1. **IUnitOfWork.SaveChangesAsync** + +```csharp +await _unitOfWorkManager.Current.SaveChangesAsync(); +``` + +2. **autoSave parameter in repositories** + +```csharp +await _repository.InsertAsync(entity, autoSave: true); +``` + +Both `autoSave` and `SaveChangesAsync` commit changes in the current context to the database. However, these are not applied until `CompleteAsync` is called. If the unit of work throws an exception or `CompleteAsync` is not called, the transaction will be rolled back. It means all the DB operations will be reverted back. Only after successfully executing `CompleteAsync` will the transaction be permanently committed to the database. + +3. **CompleteAsync** + +```csharp +using (var uow = _unitOfWorkManager.Begin()) +{ + // Execute database operations + await uow.CompleteAsync(); +} +``` + +When you manually control the Unit of Work with `UnitOfWorkManager`, the `CompleteAsync` method is crucial for transaction completion. The unit of work maintains a `DbTransaction` object internally, and the `CompleteAsync` method invokes `DbTransaction.CommitAsync` to commit the transaction. The transaction will not be committed if `CompleteAsync` is either not executed or fails to execute successfully. + +This method not only commits all database transactions but also: + +- Executes and processes all pending domain events within the Unit of Work +- Executes all registered post-operations and cleanup tasks within the Unit of Work +- Releases all DbTransaction resources upon disposal of the Unit of Work object + +> Note: `CompleteAsync` method should be called only once. Multiple calls are not supported. + +#### In Non-Transactional Unit of Work + +In non-transactional Unit of Work, these methods behave differently: + +Both `autoSave` and `SaveChangesAsync` will persist changes to the database immediately, and these changes cannot be rolled back. Even in non-transactional Unit of Work, calling the `CompleteAsync` method remains necessary as it handles other essential tasks. + +Example: +```csharp +using (var uow = _unitOfWorkManager.Begin(isTransactional: false)) +{ + // Changes are persisted immediately and cannot be rolled back + await _repository.InsertAsync(entity1, autoSave: true); + + // This operation persists independently of the previous operation + await _repository.InsertAsync(entity2, autoSave: true); + + await uow.CompleteAsync(); +} +``` + +### Methods to Roll Back Transactions + +#### In Transactional Unit of Work + +A unit of work provides multiple approaches to roll back transactions: + +1. **Automatic Rollback** + +For transactions automatically managed by the ABP Framework, any uncaught exceptions during the request will trigger an automatic rollback. + +2. **Manual Rollback** + +For manually managed transactions, you can explicitly invoke the `RollbackAsync` method to immediately roll back the current transaction. + +> Important: Once `RollbackAsync` is called, the entire Unit of Work transaction will be rolled back immediately, and any subsequent calls to `CompleteAsync` will have no effect. + +```csharp +using (var uow = _unitOfWorkManager.Begin( + isTransactional: true, + isolationLevel: IsolationLevel.RepeatableRead, + timeout: 30 +)) +{ + await _repository.InsertAsync(entity); + + if (someCondition) + { + await uow.RollbackAsync(); + return; + } + + await uow.CompleteAsync(); +} +``` + +The `CompleteAsync` method attempts to commit the transaction. If any exceptions occur during this process, the transaction will not be committed. + +Here are two common exception scenarios: + +1. **Exception Handling Within Unit of Work** + +```csharp +using (var uow = _unitOfWorkManager.Begin( + isTransactional: true, + isolationLevel: IsolationLevel.RepeatableRead, + timeout: 30 +)) +{ + try + { + await _bookRepository.InsertAsync(book); + await uow.SaveChangesAsync(); + await _productRepository.UpdateAsync(product); + await uow.CompleteAsync(); + } + catch (Exception) + { + // Exceptions can occur in InsertAsync, SaveChangesAsync, UpdateAsync, or CompleteAsync + // Even if some operations succeed, the transaction remains uncommitted to the database + // While you can explicitly call RollbackAsync to roll back the transaction, + // the transaction will not be committed anyway if CompleteAsync fails to execute + throw; + } +} +``` + +2. **Exception Handling Outside Unit of Work** + +```csharp +try +{ + using (var uow = _unitOfWorkManager.Begin( + isTransactional: true, + isolationLevel: IsolationLevel.RepeatableRead, + timeout: 30 + )) + { + await _bookRepository.InsertAsync(book); + await uow.SaveChangesAsync(); + await _productRepository.UpdateAsync(product); + await uow.CompleteAsync(); + } +} +catch (Exception) +{ + // Exceptions can occur in UpdateAsync, SaveChangesAsync, UpdateAsync, or CompleteAsync + // Even if some operations succeed, the transaction remains uncommitted to the database + // Since CompleteAsync was not successfully executed, the transaction will not be committed + throw; +} +``` + +#### In Non-Transactional Unit of Work + +In non-transactional units of work, operations are irreversible. Changes saved using `autoSave: true` or `SaveChangesAsync()` are persisted immediately, and the `RollbackAsync` method has no effect. + +## Transaction Management Best Practices + +### 1. Remember to Commit Transactions + +When manually controlling transactions, remember to call the `CompleteAsync` method to commit the transaction after operations are complete. + +### 2. Pay Attention to Context + +If a unit of work already exists in the current context, `UnitOfWorkManager.Begin` method and` UnitOfWorkAttribute` will **reuse it**. Specify `requiresNew: true` to force create a new unit of work. + +```csharp +[UnitOfWork] +public async Task Method1() +{ + using (var uow = _unitOfWorkManager.Begin( + requiresNew: true, + isTransactional: true, + isolationLevel: IsolationLevel.RepeatableRead, + timeout: 30 + )) + { + await Method2(); + await uow.CompleteAsync(); + } +} +``` + +### 3. Use `virtual` Methods + +To be able to use Unit of Work attribute, you must use the `virtual` modifier for methods in dependency injection class services, because ABP Framework uses interceptors, and it cannot intercept non `virtual` methods, thus unable to implement Unit of Work functionality. + +### 4. Avoid Long Transactions + +Enabling long-running transactions can lead to resource locking, excessive transaction log usage, and reduced concurrent performance, while rollback costs are high and may exhaust database connection resources. It's recommended to split into shorter transactions, reduce lock holding time, and optimize performance and reliability. + +## Transaction-Related Recommendations + +- Choose appropriate transaction isolation levels based on business requirements +- Avoid overly long transactions, long-running operations should be split into multiple small transactions +- Use the `requiresNew` parameter reasonably to control transaction boundaries +- Pay attention to setting appropriate transaction timeout periods +- Ensure transactions can properly roll back when exceptions occur +- For read-only operations, it's recommended to use non-transactional Unit of Work to improve performance + +## References + +- [ABP Unit of Work](https://abp.io/docs/latest/framework/architecture/domain-driven-design/unit-of-work) +- [EF Core Transactions](https://docs.microsoft.com/en-us/ef/core/saving/transactions) +- [Transaction Isolation Levels](https://docs.microsoft.com/en-us/dotnet/api/system.data.isolationlevel) diff --git a/docs/en/Community-Articles/2025-01-24-Understanding-Transactions-in-ABP-Unit-Of-Work/pic.png b/docs/en/Community-Articles/2025-01-24-Understanding-Transactions-in-ABP-Unit-Of-Work/pic.png new file mode 100644 index 00000000000..d6805a806e3 Binary files /dev/null and b/docs/en/Community-Articles/2025-01-24-Understanding-Transactions-in-ABP-Unit-Of-Work/pic.png differ diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index 8253b732df1..8ca147a3dbd 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -1430,6 +1430,10 @@ "text": "LeptonX Lite", "path": "ui-themes/lepton-x-lite/blazor.md" }, + { + "text": "LeptonX", + "path": "ui-themes/lepton-x/blazor.md" + }, { "text": "Branding", "path": "framework/ui/blazor/branding.md" @@ -1751,6 +1755,10 @@ { "text": "LeptonX Lite", "path": "ui-themes/lepton-x-lite/angular.md" + }, + { + "text": "LeptonX", + "path": "ui-themes/lepton-x/angular.md" } ] }, diff --git a/docs/en/framework/ui/blazor/overall.md b/docs/en/framework/ui/blazor/overall.md index 12cef14552a..54a545759e5 100644 --- a/docs/en/framework/ui/blazor/overall.md +++ b/docs/en/framework/ui/blazor/overall.md @@ -1,14 +1,17 @@ # Blazor UI: Overall -## Introduction +[Blazor](https://docs.microsoft.com/en-us/aspnet/core/blazor/) is a framework for building interactive client-side web UI with .NET. It enables .NET developers to create Single-Page Web Applications using C# and the Razor syntax. -[Blazor](https://docs.microsoft.com/en-us/aspnet/core/blazor/) is a framework for building interactive client-side web UI with .NET. It is promising for a .NET developer that you can create Single-Page Web Applications using C# and the Razor syntax. +ABP provides comprehensive infrastructure and integrations that make your Blazor development easier, comfortable and enjoyable. ABP supports multiple Blazor hosting models: -ABP provides infrastructure and integrations that make your Blazor development even easier, comfortable and enjoyable. +* **Blazor WebAssembly (WASM)**: Client-side hosting model where the entire application runs in the browser using WebAssembly +* **Blazor Server**: Server-side hosting model with a real-time SignalR connection +* **Blazor WebApp**: The new hybrid/united model introduced in .NET 8 combining the benefits of Server and WebAssembly approaches +* **MAUI Blazor**: For building cross-platform native applications using Blazor & MAUI -This document provides an overview for the ABP Blazor UI integration and highlights some major features. +This document provides an overview of the ABP Blazor UI integration and highlights some major features. -### Getting Started +## Getting Started You can follow the documents below to start with the ABP and the Blazor UI now: @@ -94,7 +97,7 @@ These libraries are selected as the base libraries and available to the applicat > Bootstrap's JavaScript part is not used since the Blazorise library already provides the necessary functionalities to the Bootstrap components in a native way. -> Beginning from June, 2021, the Blazorise library has dual licenses; open source & commercial. Based on your yearly revenue, you may need to buy a commercial license. See [this post](https://blazorise.com/news/announcing-2022-blazorise-plans-and-pricing-updates) to learn more. The Blazorise license is bundled with ABP and commercial customers doesn’t need to buy an extra Blazorise license. +> Beginning from June, 2021, the Blazorise library has dual licenses; open source & commercial. Based on your yearly revenue, you may need to buy a commercial license. See [this post](https://blazorise.com/news/announcing-2022-blazorise-plans-and-pricing-updates) to learn more. The Blazorise license is bundled with ABP and commercial customers doesn't need to buy an extra Blazorise license. ### The Layout diff --git a/docs/en/framework/ui/blazor/theming.md b/docs/en/framework/ui/blazor/theming.md index 0c7d6f84067..9a056580bba 100644 --- a/docs/en/framework/ui/blazor/theming.md +++ b/docs/en/framework/ui/blazor/theming.md @@ -27,7 +27,7 @@ Currently, three themes are **officially provided**: * The [Basic Theme](basic-theme.md) is the minimalist theme with the plain Bootstrap style. It is **open source and free**. * The [Lepton Theme](https://abp.io/themes) is a **commercial** theme developed by the core ABP team and is a part of the [ABP](https://abp.io/) license. -* The [LeptonX Theme](https://x.leptontheme.com/) is a theme that has a [commercial](https://docs.abp.io/en/commercial/latest/themes/lepton-x/blazor) and a [lite](../../../ui-themes/lepton-x-lite/blazor.md) version. +* The [LeptonX Theme](https://x.leptontheme.com/) is a theme that has a [commercial](../../../ui-themes/lepton-x/blazor.md) and a [lite](../../../ui-themes/lepton-x-lite/blazor.md) version. ## Overall diff --git a/docs/en/framework/ui/mvc-razor-pages/tag-helpers/form-elements.md b/docs/en/framework/ui/mvc-razor-pages/tag-helpers/form-elements.md index 9230075ed30..b59ecf8dd3b 100644 --- a/docs/en/framework/ui/mvc-razor-pages/tag-helpers/form-elements.md +++ b/docs/en/framework/ui/mvc-razor-pages/tag-helpers/form-elements.md @@ -10,7 +10,7 @@ See the [form elements demo page](https://bootstrap-taghelpers.abp.io/Components ## abp-input -`abp-input` tag creates a Bootstrap form input for a given c# property. It uses [Asp.Net Core Input Tag Helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms?view=aspnetcore-7.0#the-input-tag-helper) in background, so every data annotation attribute of `input` tag helper of Asp.Net Core is also valid for `abp-input`. +`abp-input` tag creates a Bootstrap form input for a given c# property. It uses [Asp.Net Core Input Tag Helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms?view=aspnetcore-9.0#the-input-tag-helper) in background, so every data annotation attribute of `input` tag helper of Asp.Net Core is also valid for `abp-input`. Usage: @@ -89,7 +89,7 @@ You can set some of the attributes on your c# property, or directly on HTML tag. * `required-symbol`: Adds the required symbol `(*)` to the label when the input is required. The default value is `True`. * `floating-label`: Sets the label as floating label. The default value is `False`. -`asp-format`, `name` and `value` attributes of [Asp.Net Core Input Tag Helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms?view=aspnetcore-7.0#the-input-tag-helper) are also valid for `abp-input` tag helper. +`asp-format`, `name` and `value` attributes of [Asp.Net Core Input Tag Helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms?view=aspnetcore-9.0#the-input-tag-helper) are also valid for `abp-input` tag helper. ### Label & Localization @@ -101,7 +101,7 @@ You can set the label of the input in several ways: ## abp-select -`abp-select` tag creates a Bootstrap form select for a given c# property. It uses [ASP.NET Core Select Tag Helper](https://docs.microsoft.com/tr-tr/aspnet/core/mvc/views/working-with-forms?view=aspnetcore-3.1#the-select-tag-helper) in background, so every data annotation attribute of `select` tag helper of ASP.NET Core is also valid for `abp-select`. +`abp-select` tag creates a Bootstrap form select for a given c# property. It uses [ASP.NET Core Select Tag Helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms?view=aspnetcore-9.0#the-select-tag-helper) in background, so every data annotation attribute of `select` tag helper of ASP.NET Core is also valid for `abp-select`. `abp-select` tag needs a list of `Microsoft.AspNetCore.Mvc.Rendering.SelectListItem ` to work. It can be provided by `asp-items` attriube on the tag or `[SelectItems()]` attribute on c# property. (if you are using [abp-dynamic-form](dynamic-forms.md), c# attribute is the only way.) @@ -432,4 +432,4 @@ newPicker.insertAfter($('body')); * `startDateName`: Sets the name of the hidden start date input. * `endDateName`: Sets the name of the hidden end date input. * `dateName`: Sets the name of the hidden date input. -* Other [datepicker options](https://www.daterangepicker.com/#options). Eg: `startDate: "2020-01-01"`. \ No newline at end of file +* Other [datepicker options](https://www.daterangepicker.com/#options). Eg: `startDate: "2020-01-01"`. diff --git a/docs/en/modules/account/idle-session-timeout.md b/docs/en/modules/account/idle-session-timeout.md index 069e6db665e..231ea31e7dc 100644 --- a/docs/en/modules/account/idle-session-timeout.md +++ b/docs/en/modules/account/idle-session-timeout.md @@ -6,7 +6,7 @@ The `Idle Session Timeout` feature allows you to automatically log out users aft You can enable/disable the `Idle Session Timeout` feature in the `Setting > Account > Idle Session Timeout` page. -The default idle session timeout is 1 hour. You can change it by selecting a different value from the dropdown list or entering a custom value(in minutes). +The default idle session timeout is 1 hour. You can change it by selecting a different value from the dropdown list or entering a custom value (in minutes). ![idle-setting](../../images/idle-setting.png) diff --git a/docs/en/solution-templates/layered-web-application/deployment/azure-deployment/step3-deployment-github-action.md b/docs/en/solution-templates/layered-web-application/deployment/azure-deployment/step3-deployment-github-action.md index 9aa9e5df83d..d6533ef6ec4 100644 --- a/docs/en/solution-templates/layered-web-application/deployment/azure-deployment/step3-deployment-github-action.md +++ b/docs/en/solution-templates/layered-web-application/deployment/azure-deployment/step3-deployment-github-action.md @@ -295,7 +295,7 @@ jobs: environment: name: 'Production' url: ${{ steps.deploy-to-webapp-3.outputs.webapp-url }} - + steps: - name: Download artifact from apihost uses: actions/download-artifact@v4 with: diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Pagination/AbpPaginationTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Pagination/AbpPaginationTagHelperService.cs index 9d1bac7f5f0..115d6eeb021 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Pagination/AbpPaginationTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Pagination/AbpPaginationTagHelperService.cs @@ -106,11 +106,11 @@ protected virtual async Task GetPageAsync(TagHelperContext context, TagH protected virtual async Task GetPreviousButtonAsync(TagHelperContext context, TagHelperOutput output) { var localizationKey = "PagerPrevious"; - var currentPage = TagHelper.Model.CurrentPage == 1 - ? TagHelper.Model.CurrentPage.ToString() - : (TagHelper.Model.CurrentPage - 1).ToString(); + var currentPage = TagHelper.Model.CurrentPage > 1 + ? (TagHelper.Model.CurrentPage - 1).ToString() + : "1"; return - "
  • \r\n" + + "
  • \r\n" + (await RenderAnchorTagHelperLinkHtmlAsync(context, output, currentPage, localizationKey)) + "
  • "; } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityUserRepository.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityUserRepository.cs index f17f74b6029..8da3237f837 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityUserRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityUserRepository.cs @@ -69,6 +69,7 @@ Task> GetListAsync( bool includeDetails = false, Guid? roleId = null, Guid? organizationUnitId = null, + Guid? id = null, string userName = null, string phoneNumber = null, string emailAddress = null, @@ -114,6 +115,7 @@ Task GetCountAsync( string filter = null, Guid? roleId = null, Guid? organizationUnitId = null, + Guid? id = null, string userName = null, string phoneNumber = null, string emailAddress = null, diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs index 86682b85db7..a6238946254 100644 --- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs @@ -62,35 +62,35 @@ public virtual async Task> GetRoleNamesAsync( { var dbContext = await GetDbContextAsync(); var userRoles = await (from userRole in dbContext.Set() - join role in dbContext.Roles on userRole.RoleId equals role.Id - where userIds.Contains(userRole.UserId) - group new - { - userRole.UserId, - role.Name - } by userRole.UserId + join role in dbContext.Roles on userRole.RoleId equals role.Id + where userIds.Contains(userRole.UserId) + group new { + userRole.UserId, + role.Name + } by userRole.UserId into gp - select new IdentityUserIdWithRoleNames - { - Id = gp.Key, RoleNames = gp.Select(x => x.Name).ToArray() - }).ToListAsync(cancellationToken: cancellationToken); + select new IdentityUserIdWithRoleNames + { + Id = gp.Key, + RoleNames = gp.Select(x => x.Name).ToArray() + }).ToListAsync(cancellationToken: cancellationToken); var orgUnitRoles = await (from userOu in dbContext.Set() - join roleOu in dbContext.Set() on userOu.OrganizationUnitId equals roleOu.OrganizationUnitId - join role in dbContext.Roles on roleOu.RoleId equals role.Id - where userIds.Contains(userOu.UserId) - group new - { - userOu.UserId, - role.Name - } by userOu.UserId + join roleOu in dbContext.Set() on userOu.OrganizationUnitId equals roleOu.OrganizationUnitId + join role in dbContext.Roles on roleOu.RoleId equals role.Id + where userIds.Contains(userOu.UserId) + group new { + userOu.UserId, + role.Name + } by userOu.UserId into gp - select new IdentityUserIdWithRoleNames - { - Id = gp.Key, RoleNames = gp.Select(x => x.Name).ToArray() - }).ToListAsync(cancellationToken: cancellationToken); + select new IdentityUserIdWithRoleNames + { + Id = gp.Key, + RoleNames = gp.Select(x => x.Name).ToArray() + }).ToListAsync(cancellationToken: cancellationToken); - return userRoles.Concat(orgUnitRoles).GroupBy(x => x.Id).Select(x => new IdentityUserIdWithRoleNames {Id = x.Key, RoleNames = x.SelectMany(y => y.RoleNames).Distinct().ToArray()}).ToList(); + return userRoles.Concat(orgUnitRoles).GroupBy(x => x.Id).Select(x => new IdentityUserIdWithRoleNames { Id = x.Key, RoleNames = x.SelectMany(y => y.RoleNames).Distinct().ToArray() }).ToList(); } public virtual async Task> GetRoleNamesInOrganizationUnitAsync( @@ -196,6 +196,7 @@ public virtual async Task> GetListAsync( bool includeDetails = false, Guid? roleId = null, Guid? organizationUnitId = null, + Guid? id = null, string userName = null, string phoneNumber = null, string emailAddress = null, @@ -215,6 +216,7 @@ public virtual async Task> GetListAsync( filter, roleId, organizationUnitId, + id, userName, phoneNumber, emailAddress, @@ -272,6 +274,7 @@ public virtual async Task GetCountAsync( string filter = null, Guid? roleId = null, Guid? organizationUnitId = null, + Guid? id = null, string userName = null, string phoneNumber = null, string emailAddress = null, @@ -291,6 +294,7 @@ public virtual async Task GetCountAsync( filter, roleId, organizationUnitId, + id, userName, phoneNumber, emailAddress, @@ -434,6 +438,7 @@ protected virtual async Task> GetFilteredQueryableAsync string filter = null, Guid? roleId = null, Guid? organizationUnitId = null, + Guid? id = null, string userName = null, string phoneNumber = null, string emailAddress = null, @@ -451,6 +456,11 @@ protected virtual async Task> GetFilteredQueryableAsync { var upperFilter = filter?.ToUpperInvariant(); var query = await GetQueryableAsync(); + + if (id.HasValue) + { + return query.Where(x => x.Id == id); + } if (roleId.HasValue) { diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs index c93a41e21d8..64c47f8d2c3 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs @@ -164,6 +164,7 @@ public virtual async Task> GetListAsync( bool includeDetails = false, Guid? roleId = null, Guid? organizationUnitId = null, + Guid? id = null, string userName = null, string phoneNumber = null, string emailAddress = null, @@ -183,6 +184,7 @@ public virtual async Task> GetListAsync( filter, roleId, organizationUnitId, + id, userName, phoneNumber, emailAddress, @@ -243,6 +245,7 @@ public virtual async Task GetCountAsync( string filter = null, Guid? roleId = null, Guid? organizationUnitId = null, + Guid? id = null, string userName = null, string phoneNumber = null, string emailAddress = null, @@ -262,6 +265,7 @@ public virtual async Task GetCountAsync( filter, roleId, organizationUnitId, + id, userName, phoneNumber, emailAddress, @@ -398,6 +402,7 @@ public virtual async Task> GetRoleNamesAsync( var allOrganizationUnitRoleIds = organizationUnitAndRoleIds.SelectMany(x => x.Roles.Select(r => r.RoleId)).ToList(); var allRoleIds = roleIds.Union(allOrganizationUnitRoleIds); + var roles = await (await GetQueryableAsync(cancellationToken)).Where(r => allRoleIds.Contains(r.Id)).Select(r => new{ r.Id, r.Name }).ToListAsync(cancellationToken); var userRoles = userAndRoleIds.ToDictionary(x => x.Key, x => roles.Where(r => x.Value.Contains(r.Id)).Select(r => r.Name).ToArray()); @@ -412,9 +417,9 @@ public virtual async Task> GetRoleNamesAsync( { user.RoleNames = user.RoleNames.Union(roleNames).ToArray(); } - else if(roleNames.Any()) + else if (roleNames.Any()) { - result.Add(new IdentityUserIdWithRoleNames { Id = userAndOrganizationUnitId.Key, RoleNames = roleNames}); + result.Add(new IdentityUserIdWithRoleNames { Id = userAndOrganizationUnitId.Key, RoleNames = roleNames }); } } @@ -425,6 +430,7 @@ protected virtual async Task> GetFilteredQueryableAsync string filter = null, Guid? roleId = null, Guid? organizationUnitId = null, + Guid? id = null, string userName = null, string phoneNumber = null, string emailAddress = null, @@ -443,6 +449,11 @@ protected virtual async Task> GetFilteredQueryableAsync var upperFilter = filter?.ToUpperInvariant(); var query = await GetQueryableAsync(cancellationToken); + if (id.HasValue) + { + return query.Where(x => x.Id == id); + } + if (roleId.HasValue) { var organizationUnitIds = (await GetQueryableAsync(cancellationToken)) diff --git a/npm/ng-packs/packages/schematics/src/utils/service.ts b/npm/ng-packs/packages/schematics/src/utils/service.ts index b32dd5fba51..8f48ed36930 100644 --- a/npm/ng-packs/packages/schematics/src/utils/service.ts +++ b/npm/ng-packs/packages/schematics/src/utils/service.ts @@ -18,6 +18,7 @@ import { createTypeAdapter, createTypeParser, createTypesToImportsReducer, + getTypeForEnumList, removeTypeModifiers, } from './type'; import { eBindingSourceId } from '../enums'; @@ -80,7 +81,19 @@ export function createActionToBodyMapper() { const adaptType = createTypeAdapter(); return ({ httpMethod, parameters, returnValue, url }: Action) => { - const responseType = adaptType(returnValue.typeSimple); + let responseType = adaptType(returnValue.typeSimple); + if (responseType.includes('enum')) { + const type = returnValue.typeSimple.replace('enum', returnValue.type); + + if (responseType === 'enum') { + responseType = adaptType(type); + } + + if (responseType === 'enum[]') { + const normalizedType = getTypeForEnumList(type); + responseType = adaptType(normalizedType); + } + } const responseTypeWithNamespace = returnValue.typeSimple; const body = new Body({ method: httpMethod, responseType, url, responseTypeWithNamespace }); @@ -109,7 +122,12 @@ export function createActionToSignatureMapper() { if (isFormData || isFormArray) { return new Property({ name: p.name, type: 'FormData' }); } - const type = adaptType(p.typeSimple); + + let type = adaptType(p.typeSimple); + if (p.typeSimple === 'enum' || p.typeSimple === '[enum]') { + type = adaptType(p.type); + } + const parameter = new Property({ name: p.name, type }); parameter.setDefault(p.defaultValue); parameter.setOptional(p.isOptional); @@ -183,7 +201,9 @@ function createActionToImportsReducer( parseGenerics(paramType) .toGenerics() .forEach(type => { - if (types[type]) acc.push({ type, isEnum: types[type].isEnum }); + if (types[type]) { + acc.push({ type, isEnum: types[type].isEnum }); + } }), ); diff --git a/npm/ng-packs/packages/schematics/src/utils/type.ts b/npm/ng-packs/packages/schematics/src/utils/type.ts index 63c69c7aec4..f6d16b39df6 100644 --- a/npm/ng-packs/packages/schematics/src/utils/type.ts +++ b/npm/ng-packs/packages/schematics/src/utils/type.ts @@ -58,6 +58,10 @@ export function removeTypeModifiers(type: string) { return type.replace(/\[\]/g, ''); } +export function getTypeForEnumList(type: string) { + return type.replace(/^.*<([^>]+)>.*$/, '[$1]'); +} + export function createTypesToImportsReducer(solution: string, namespace: string) { const mapTypeToImport = createTypeToImportMapper(solution, namespace); @@ -68,7 +72,7 @@ export function createTypesToImportsReducer(solution: string, namespace: string) return; } - if(newImport.specifiers.some(f => f.toLocaleLowerCase() === type.toLocaleLowerCase())){ + if (newImport.specifiers.some(f => f.toLocaleLowerCase() === type.toLocaleLowerCase())) { return; } @@ -76,7 +80,7 @@ export function createTypesToImportsReducer(solution: string, namespace: string) ({ keyword, path }) => keyword === newImport.keyword && path === newImport.path, ); - if (!existingImport){ + if (!existingImport) { return imports.push(newImport); } @@ -101,7 +105,7 @@ export function createTypeToImportMapper(solution: string, namespace: string) { const refs = [removeTypeModifiers(type)]; const specifiers = [adaptType(simplifyType(refs[0]).split('<')[0])]; let path = relativePathToModel(namespace, modelNamespace); - + if (VOLO_REGEX.test(type)) { path = '@abp/ng.core'; }