@ -0,0 +1,147 @@ |
|||
# ABP Platform 9.1 RC Has Been Released |
|||
|
|||
We are happy to release [ABP](https://abp.io) version **9.1 RC** (Release Candidate). This blog post introduces the new features and important changes in this new version. |
|||
|
|||
Try this version and provide feedback for a more stable version of ABP v9.1! Thanks to you in advance. |
|||
|
|||
## Get Started with the 9.1 RC |
|||
|
|||
You can check the [Get Started page](https://abp.io/get-started) to see how to get started with ABP. You can either download [ABP Studio](https://abp.io/get-started#abp-studio-tab) (**recommended**, if you prefer a user-friendly GUI application - desktop application) or use the [ABP CLI](https://abp.io/docs/latest/cli). |
|||
|
|||
By default, ABP Studio uses stable versions to create solutions. Therefore, if you want to create a solution with a preview version, first you need to create a solution and then switch your solution to the preview version from the ABP Studio UI: |
|||
|
|||
 |
|||
|
|||
## Migration Guide |
|||
|
|||
There are no breaking changes in this version that would affect your application. Only you might need to update some constant names due to the OpenIddict 6.0 upgrade, which is explained in the [OpenIddict 6.0 migration guide](https://abp.io/docs/9.1/release-info/migration-guides/openiddict5-to-6). |
|||
|
|||
## What's New with ABP v9.1? |
|||
|
|||
In this section, I will introduce some major features released in this version. |
|||
Here is a brief list of titles explained in the next sections: |
|||
|
|||
* Upgraded to Angular 19 |
|||
* Upgraded to OpenIddict 6.0 |
|||
* New Blazor WASM Bundling System |
|||
* Idle Session Warning |
|||
* Lazy Expandable Feature for Documentation |
|||
|
|||
### Upgraded to Angular 19 |
|||
|
|||
We've upgraded the Angular templates and packages to **Angular 19**. This upgrade brings the latest features and improvements from the Angular ecosystem to ABP-based applications, including better performance and development experience. |
|||
|
|||
### Upgraded to OpenIddict 6.0 |
|||
|
|||
OpenIddict 6.0 has been released and we've upgraded the OpenIddict packages to version 6.0 in ABP 9.1. This brings enhanced security features and improved authentication capabilities. The migration is straightforward and mainly involves updating some constant names: |
|||
|
|||
- `OpenIddictConstants.Permissions.Endpoints.Logout` is now `OpenIddictConstants.Permissions.Endpoints.EndSession` |
|||
- `OpenIddictConstants.Permissions.Endpoints.Device` is now `OpenIddictConstants.Permissions.Endpoints.DeviceAuthorization` |
|||
|
|||
If you're using IdentityModel packages directly, you'll need to upgrade them to the latest stable version (8.3.0). This update ensures your applications stay current with the latest security standards and best practices. |
|||
|
|||
> Please refer to the [OpenIddict 6.0 migration guide](https://abp.io/docs/9.1/release-info/migration-guides/openiddict5-to-6) for more information. |
|||
|
|||
### New Blazor WASM Bundling System |
|||
|
|||
We've implemented a new bundling system for Blazor WebAssembly applications that eliminates the need to manually run the `abp bundle` command. This system automatically handles JavaScript and CSS file bundling at runtime, significantly improving both development experience and application loading performance. |
|||
|
|||
**Key improvements include:** |
|||
|
|||
- Automatic bundling of JavaScript and CSS files without manual intervention |
|||
- Dynamic file generation through the host application |
|||
- Better integration with the ABP module system |
|||
- Improved asset management through virtual file system |
|||
|
|||
The new system is particularly beneficial for modular applications, as it allows modules to contribute their assets automatically to the global bundles. This results in a more maintainable and efficient asset management system for Blazor WebAssembly applications. |
|||
|
|||
> Please refer to [this documentation](https://abp.io/docs/9.1/framework/ui/blazor/global-scripts-styles) for more information. |
|||
|
|||
### Idle Session Warning |
|||
|
|||
We've introduced a new idle session warning feature for the [Account (Pro) Module](https://abp.io/docs/latest/modules/account-pro) that helps manage user sessions more effectively. This security enhancement automatically monitors user activity and manages session timeouts in a user-friendly way. |
|||
|
|||
 |
|||
|
|||
The feature can be easily configured through the administration interface, where administrators can: |
|||
|
|||
- Enable/disable the idle session timeout |
|||
- Set custom timeout duration in minutes |
|||
- Configure when users should be signed out |
|||
|
|||
When a user becomes inactive for the configured duration, they'll receive a warning dialog: |
|||
|
|||
 |
|||
|
|||
**Key features and behaviors:** |
|||
|
|||
- Tracks real user activity (mouse movements, keyboard presses) across all tabs |
|||
- Works on a per-browser session basis - affects all tabs of the same session |
|||
- Maintains session if user is active in any tab of the application |
|||
- Provides a countdown timer before automatic sign-out |
|||
- Offers options to "Stay signed in" or "Sign out now" |
|||
|
|||
This feature significantly improves application security while maintaining a smooth user experience by preventing unexpected session expirations and data loss. |
|||
|
|||
### Lazy Expandable Feature for Documentation |
|||
|
|||
We've introduced a new lazy expandable feature to the documentation system that significantly improves navigation through large documentation sections. This enhancement addresses common challenges when dealing with extensive documentation hierarchies by introducing smart menu management. |
|||
|
|||
**Key benefits and features:** |
|||
|
|||
- **Cleaner Navigation:** The menu stays concise by hiding sub-items until they're needed, reducing visual clutter |
|||
- **Better Performance:** Reduces the initial load of the navigation tree by loading sub-items on demand |
|||
- **Improved Search Experience:** Makes filtering documentation items more efficient by showing only relevant top-level items |
|||
- **Context-Aware Expansion:** Automatically expands relevant sections when viewing specific documentation pages |
|||
|
|||
The feature works by marking certain documentation sections as "lazy expandable" in the navigation configuration. When users navigate to a document within a lazy expandable section, the system automatically expands the relevant menu items while keeping other sections collapsed. |
|||
|
|||
This improvement is particularly valuable for complex documentation areas like tutorials, solution templates, and extensive module documentation, where having all navigation items visible at once could be overwhelming. |
|||
|
|||
An example of lazy expandable feature from the [ABP's BookStore Tutorial](https://abp.io/docs/latest/tutorials/book-store/part-01): |
|||
|
|||
```json |
|||
{ |
|||
"text": "Book Store Application", |
|||
"isLazyExpandable": true, |
|||
"path": "tutorials/book-store", |
|||
"items": [ |
|||
{ |
|||
"text": "Overview", |
|||
"path": "tutorials/book-store", |
|||
"isIndex": true |
|||
}, |
|||
//other items... |
|||
] |
|||
} |
|||
``` |
|||
|
|||
 |
|||
|
|||
### Others |
|||
|
|||
Some other highlights from this release: |
|||
|
|||
* Updated Iyzico NuGet packages to the latest version, which is used in the [ABP's Payment Module](https://abp.io/docs/latest/modules/payment#payment-module-pro). |
|||
* Removed optional _secondaryIds_ from path. See: [#21307](https://github.com/abpframework/abp/pull/21307) |
|||
* [CMS Kit Pro](https://abp.io/docs/latest/modules/cms-kit-pro): Added automatic deletion of comments when a blog post is deleted - comments are now automatically removed when their associated blog post is deleted. |
|||
* Avoiding global blocking in distributed event handlers (See [#21716](https://github.com/abpframework/abp/pull/21716)). |
|||
|
|||
## Community News |
|||
|
|||
### New ABP Community Articles |
|||
|
|||
There are exciting articles contributed by the ABP community as always. I will highlight some of them here: |
|||
|
|||
* [Integrating ABP Modules in Your ASP.NET Core Web API Project. A Step-by-Step Guide](https://abp.io/community/articles/integrating-abp-modules-in-your-asp.net-core-web-api-project.-a-stepbystep-guide-jtbyosnr) by [Sajankumar Vijayan](https://abp.io/community/members/connect) |
|||
* [ABP Framework: Background Jobs vs Background Workers](https://abp.io/community/articles/abp-framework-background-jobs-vs-background-workers-when-to-use-which-t98pzjv6) — When to Use Which? by [Alper Ebiçoğlu](https://twitter.com/alperebicoglu) |
|||
* [The new Unit Test structure in ABP application](https://abp.io/community/articles/the-new-unit-test-structure-in-abp-application-4vvvp2oy) by [Liming Ma](https://github.com/maliming) |
|||
* [How to Use OpenAI API with ABP Framework](https://abp.io/community/articles/how-to-use-openai-api-with-abp-framework-rsfvihla) by [Berkan Şaşmaz](https://github.com/berkansasmaz) |
|||
|
|||
Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://abp.io/community/posts/submit) to the ABP Community. |
|||
|
|||
## Conclusion |
|||
|
|||
This version comes with some new features and a lot of enhancements to the existing features. You can see the [Road Map](https://abp.io/docs/9.1/release-info/road-map) documentation to learn about the release schedule and planned features for the next releases. Please try ABP v9.1 RC and provide feedback to help us release a more stable version. |
|||
|
|||
Thanks for being a part of this community! |
|||
|
After Width: | Height: | Size: 482 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 34 KiB |
@ -0,0 +1,46 @@ |
|||
# ABP Studio Now Supports MacOS Intel 🚀 |
|||
|
|||
We are excited to announce that [ABP Studio, our cross-platform desktop application for ABP developers](https://abp.io/studio), now supports Intel-based Mac computers! |
|||
|
|||
This addition expands our platform compatibility, ensuring that developers using Intel-powered Macs can also benefit from the powerful features of ABP Studio. |
|||
|
|||
## What is ABP Studio? |
|||
|
|||
For those who aren't familiar, [ABP Studio](https://abp.io/studio) is a powerful desktop application that makes ABP development faster and easier. It offers: |
|||
|
|||
* Easy creation of new solutions (from simple applications to microservices) |
|||
* Visual architecture management for modular-monolith and microservice solutions |
|||
* Solution exploration tools for entities, services, and HTTP APIs |
|||
* Simplified running, debugging and monitoring of multi-application or microservice solutions |
|||
* Kubernetes cluster integration capabilities |
|||
* and more... |
|||
|
|||
## Extended Platform Support |
|||
|
|||
ABP Studio has been proudly supporting multiple platforms, and we're excited to add MacOS Intel to the our list of supported architectures. You can now use ABP Studio on: |
|||
|
|||
* Windows x64 |
|||
* Windows ARM |
|||
* MacOS Apple Silicon (M1/M2/M3) |
|||
* MacOS Intel **(New!)** |
|||
|
|||
## Why This Matters |
|||
|
|||
This update is particularly important for developers who are using Intel-based Mac computers. Previously, ABP Studio was only available for Apple Silicon Macs (for MacOS), but we understand that many developers are still using Intel-based Macs. With this release, we're ensuring that all Mac users can access our development tools, regardless of their processor architecture. |
|||
|
|||
## Getting Started |
|||
|
|||
Installing ABP Studio on your Intel-based Mac is straightforward: |
|||
|
|||
1. Go to [abp.io/studio](https://abp.io/studio) |
|||
2. Click on the download button and select "MacOS Intel" from the dropdown menu |
|||
3. Once downloaded, open the installer package |
|||
4. Follow the installation wizard to complete the setup |
|||
|
|||
 |
|||
|
|||
## Conclusion |
|||
|
|||
As ABP team, we're always looking for ways to improve the developer experience. By supporting Intel-based Macs, we're ensuring that all Mac users can access our development tools, regardless of their processor architecture. |
|||
|
|||
Stay tuned for more updates and enhancements as we continue to optimize ABP Studio and please provide us with your invaluable feedback. Thanks in advance! |
|||
|
After Width: | Height: | Size: 265 KiB |
|
After Width: | Height: | Size: 466 KiB |
|
After Width: | Height: | Size: 41 KiB |
@ -0,0 +1,77 @@ |
|||
# Fixing OpenIddict Certificate Issues in IIS or Azure |
|||
|
|||
When deploying an ABP application with OpenIddict to IIS or Azure, you may encounter issues with loading PFX/PKCS12 certificates. This article explains how to properly configure certificate loading to ensure it works correctly in these environments. |
|||
|
|||
## The Problem |
|||
|
|||
When running under IIS or Azure, the application pool identity may not have sufficient permissions to access certificate private keys. This commonly results in errors such as: |
|||
|
|||
- `System.Security.Cryptography.CryptographicException: Access denied.` |
|||
- `WindowsCryptographicException: Access is denied.` |
|||
- `System.Security.Cryptography.CryptographicException: The system cannot find the file specified.` |
|||
|
|||
## The Solution |
|||
|
|||
### Using AddDevelopmentEncryptionAndSigningCertificate |
|||
|
|||
For development environments using `DevelopmentEncryptionAndSigningCertificate`, you must configure the application pool to load a user profile. |
|||
|
|||
> Note: We strongly recommend using `DevelopmentEncryptionAndSigningCertificate` only in development environments. For production, always create and use a separate certificate. |
|||
|
|||
 |
|||
|
|||
### Using AddProductionEncryptionAndSigningCertificate |
|||
|
|||
The ABP OpenIddict module provides an `AddProductionEncryptionAndSigningCertificate` extension method. By default, the template project attempts to load an `openiddict.pfx` certificate in production environments. |
|||
|
|||
To ensure proper certificate loading in IIS or Azure, you need to specify appropriate `X509KeyStorageFlags` when calling this method: |
|||
|
|||
```csharp |
|||
public override void PreConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
var hostingEnvironment = context.Services.GetHostingEnvironment(); |
|||
|
|||
if (!hostingEnvironment.IsDevelopment()) |
|||
{ |
|||
PreConfigure<AbpOpenIddictAspNetCoreOptions>(options => |
|||
{ |
|||
options.AddDevelopmentEncryptionAndSigningCertificate = false; |
|||
}); |
|||
|
|||
PreConfigure<OpenIddictServerBuilder>(serverBuilder => |
|||
{ |
|||
var flag = X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.EphemeralKeySet; |
|||
serverBuilder.AddProductionEncryptionAndSigningCertificate("openiddict.pfx", "YourCertificatePassword", flag); |
|||
}); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Understanding X509KeyStorageFlags |
|||
|
|||
The configuration uses two important flags: |
|||
|
|||
* `X509KeyStorageFlags.MachineKeySet`: Specifies that the key belongs to the local computer key store, binding the key pair's lifecycle to the computer rather than a specific user. |
|||
* `X509KeyStorageFlags.EphemeralKeySet`: Indicates that the key will be stored only in memory and not persisted to disk or key store, enhancing security for runtime-only certificate requirements. |
|||
|
|||
Using these flags in combination helps prevent permission-related issues in IIS and Azure environments. |
|||
|
|||
## Troubleshooting Guide |
|||
|
|||
If you continue to experience issues, verify the following: |
|||
|
|||
* Confirm that the certificate password is correct |
|||
* Verify that the `openiddict.pfx` file exists in your deployment |
|||
* Ensure the certificate is valid - you can generate a new one using: |
|||
```bash |
|||
dotnet dev-certs https -v -ep openiddict.pfx -p YourCertificatePassword |
|||
``` |
|||
* Check the stdout logs for related errors (See [how to get stdout-log](https://abp.io/docs/latest/solution-templates/layered-web-application/deployment/deployment-iis?UI=Blazor&DB=EF&Tiered=No#how-to-get-stdout-log)) |
|||
|
|||
## References |
|||
|
|||
- [ABP OpenIddict Deployment](https://abp.io/docs/latest/solution-templates/layered-web-application/deployment/openiddict-deployment) |
|||
- [ABP IIS Deployment](https://abp.io/docs/latest/solution-templates/layered-web-application/deployment/deployment-iis) |
|||
- [ABP Azure Deployment](https://abp.io/docs/latest/solution-templates/layered-web-application/deployment/azure-deployment/azure-deployment) |
|||
- [How to Generate a New Certificate](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-dev-certs#how-to-generate-a-new-certificate) |
|||
- [Load User Profile in IIS](https://learn.microsoft.com/en-us/iis/manage/configuring-security/application-pool-identities#load-user-profile-for-an-application-pool) |
|||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 41 KiB |
@ -0,0 +1,59 @@ |
|||
# Migrating from MongoDB Driver 2 to 3 |
|||
|
|||
## Introduction |
|||
|
|||
The release of MongoDB Driver 3 includes numerous user-requested fixes and improvements that were deferred in previous versions due to backward compatibility concerns. It also features internal improvements to reduce technical debt and enhance maintainability. One major update is the removal of a significant portion of the public API (primarily from `MongoDB.Driver.Core`), which was not intended for public use. The removed APIs were marked as deprecated in version 2.30.0. |
|||
|
|||
Please refer to the [upgrade guide](https://www.mongodb.com/docs/drivers/csharp/current/upgrade/v3/) for a complete list of breaking changes and upgrade guidelines. |
|||
|
|||
## Repository Changes |
|||
|
|||
Some method signatures in the `MongoDbRepository` class have been updated because the `IMongoQueryable` has been removed. The specific changes are as follows: |
|||
|
|||
- The new `GetQueryableAsync` method has been added to return `IQueryable<TEntity>`. |
|||
- The `GetMongoQueryable` and `GetMongoQueryableAsync` methods return `IQueryable<TEntity>` instead of `IMongoQueryable<TEntity>`, |
|||
- The `GetMongoQueryable` and `GetMongoQueryableAsync` methods are marked as obsolete, You should use the new `GetQueryableAsync` method instead. |
|||
|
|||
Please update your application by searching for and replacing these method calls. |
|||
|
|||
> The return value of the `GetQueryableAsync` method is `IQueryable<TEntity>`, which can be used directly to perform queries, similar to EF Core. Remove all instances of `IMongoQueryable` in your project and replace them with `IQueryable`. |
|||
|
|||
**Previous code example:** |
|||
|
|||
```csharp |
|||
var myEntity = await (await GetMongoQueryableAsync()).As<IMongoQueryable<MyEntity>>().FirstOrDefaultAsync(x => x.Id == id); |
|||
``` |
|||
|
|||
**Updated code example:** |
|||
|
|||
```csharp |
|||
var myEntity = await GetQueryableAsync().FirstOrDefaultAsync(x => x.Id == id); |
|||
``` |
|||
|
|||
## Unit Test Changes |
|||
|
|||
Previously, we used the [EphemeralMongo](https://github.com/asimmon/ephemeral-mongo) library for unit testing. However, it does not support the latest version of [MongoDB.Driver 3.x](https://github.com/mongodb/mongo-go-driver). You should replace it with [MongoSandbox](https://github.com/wassim-k/MongoSandbox). |
|||
|
|||
In your unit test project files, replace the following: |
|||
|
|||
```xml |
|||
<PackageReference Include="EphemeralMongo.Core" Version="1.1.3" /> |
|||
<PackageReference Include="EphemeralMongo6.runtime.linux-x64" Version="1.1.3" Condition="$([MSBuild]::IsOSPlatform('Linux'))" /> |
|||
<PackageReference Include="EphemeralMongo6.runtime.osx-x64" Version="1.1.3" Condition="$([MSBuild]::IsOSPlatform('OSX'))" /> |
|||
<PackageReference Include="EphemeralMongo6.runtime.win-x64" Version="1.1.3" Condition="$([MSBuild]::IsOSPlatform('Windows'))" /> |
|||
``` |
|||
|
|||
With: |
|||
|
|||
```xml |
|||
<PackageReference Include="MongoSandbox.Core" Version="1.0.1" /> |
|||
<PackageReference Include="MongoSandbox6.runtime.linux-x64" Version="1.0.1" Condition="$([MSBuild]::IsOSPlatform('Linux'))" /> |
|||
<PackageReference Include="MongoSandbox6.runtime.osx-x64" Version="1.0.1" Condition="$([MSBuild]::IsOSPlatform('OSX'))" /> |
|||
<PackageReference Include="MongoSandbox6.runtime.win-x64" Version="1.0.1" Condition="$([MSBuild]::IsOSPlatform('Windows'))" /> |
|||
``` |
|||
|
|||
In your unit test classes, replace `using EphemeralMongo` with `using MongoSandbox`. |
|||
|
|||
## Official Upgrade Guide |
|||
|
|||
We recommend reviewing the [upgrade guide](https://www.mongodb.com/docs/drivers/csharp/current/upgrade/v3/) for MongoDB Driver 3 to ensure a smooth migration process. |
|||
@ -0,0 +1,7 @@ |
|||
# ABP Version 9.1 Migration Guide |
|||
|
|||
This document is a guide for upgrading ABP v9.0 solutions to ABP v9.1. There are no breaking changes in this version that would affect your application. |
|||
|
|||
Only you might need to update some constant names due to the OpenIddict 6.0 upgrade, which is explained in the following migration guide: |
|||
|
|||
- [OpenIddict 5.x to 6.x Migration Guide](./openiddict5-to-6.md) |
|||
@ -1,15 +0,0 @@ |
|||
using System.Collections.Generic; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQuery; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Toastr; |
|||
|
|||
[DependsOn(typeof(JQueryScriptContributor))] |
|||
public class ToastrScriptBundleContributor : BundleContributor |
|||
{ |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
context.Files.AddIfNotContains("/libs/toastr/toastr.min.js"); |
|||
} |
|||
} |
|||
@ -1,12 +0,0 @@ |
|||
using System.Collections.Generic; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Toastr; |
|||
|
|||
public class ToastrStyleBundleContributor : BundleContributor |
|||
{ |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
context.Files.AddIfNotContains("/libs/toastr/toastr.min.css"); |
|||
} |
|||
} |
|||
@ -0,0 +1,139 @@ |
|||
.abp-toast-container { |
|||
position: fixed; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: flex-end; |
|||
min-width: 350px; |
|||
min-height: 80px; |
|||
z-index: 1900; |
|||
right: 30px; |
|||
bottom: 30px; |
|||
} |
|||
|
|||
.abp-toast { |
|||
display: grid; |
|||
grid-template-columns: 35px 1fr; |
|||
gap: 5px; |
|||
margin: 5px 0; |
|||
padding: 10px; |
|||
width: 350px; |
|||
user-select: none; |
|||
z-index: 9999; |
|||
color: #fff; |
|||
border-radius: 8px; |
|||
font-size: 16px; |
|||
box-shadow: 0 0 20px 0 rgba(76, 87, 125, 0.02); |
|||
animation: toastIn 0.3s ease-in-out; |
|||
} |
|||
|
|||
.abp-toast-success { |
|||
border: 2px solid #4fbf67; |
|||
background-color: #4fbf67; |
|||
} |
|||
|
|||
.abp-toast-error { |
|||
border: 2px solid #c00d49; |
|||
background-color: #c00d49; |
|||
} |
|||
|
|||
.abp-toast-info { |
|||
border: 2px solid #438aa7; |
|||
background-color: #438aa7; |
|||
} |
|||
|
|||
.abp-toast-warning { |
|||
border: 2px solid #ff9f38; |
|||
background-color: #ff9f38; |
|||
} |
|||
|
|||
.abp-toast-icon { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.abp-toast-icon .icon { |
|||
font-size: 30px; |
|||
} |
|||
|
|||
.abp-toast-content { |
|||
position: relative; |
|||
display: flex; |
|||
align-self: center; |
|||
flex-direction: column; |
|||
word-break: break-word; |
|||
padding-bottom: 2px; |
|||
} |
|||
|
|||
.abp-toast-close-button { |
|||
position: absolute; |
|||
top: 0; |
|||
right: 0; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
margin: 0; |
|||
padding: 0px 5px 0 0; |
|||
width: 25px; |
|||
height: 100%; |
|||
border: none; |
|||
border-radius: 50%; |
|||
background: transparent; |
|||
color: inherit; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.abp-toast-close-button:focus { |
|||
outline: none; |
|||
} |
|||
|
|||
.abp-toast-title { |
|||
margin: 0; |
|||
padding: 0; |
|||
font-size: 1rem; |
|||
font-weight: 600; |
|||
} |
|||
|
|||
.abp-toast-message { |
|||
margin: 0; |
|||
padding: 0; |
|||
max-width: 240px; |
|||
} |
|||
|
|||
@keyframes toastIn { |
|||
from { |
|||
transform: translateX(100%); |
|||
opacity: 0; |
|||
} |
|||
to { |
|||
transform: translateX(0); |
|||
opacity: 1; |
|||
} |
|||
} |
|||
|
|||
@keyframes toastOut { |
|||
from { |
|||
transform: translateX(0); |
|||
opacity: 1; |
|||
} |
|||
to { |
|||
transform: translateX(100%); |
|||
opacity: 0; |
|||
} |
|||
} |
|||
|
|||
.toast-removing { |
|||
animation: toastOut 0.3s ease-in-out forwards; |
|||
} |
|||
|
|||
@media only screen and (max-width: 768px) { |
|||
.abp-toast-container { |
|||
min-width: 100%; |
|||
right: 0; |
|||
} |
|||
|
|||
.abp-toast { |
|||
width: 95%; |
|||
} |
|||
} |
|||
@ -0,0 +1,207 @@ |
|||
function AbpToastService(globalOptions) { |
|||
// Static default configuration for all instances
|
|||
if (!AbpToastService.defaultOptions) { |
|||
AbpToastService.defaultOptions = { |
|||
closable: true, |
|||
sticky: false, |
|||
life: 5000, |
|||
tapToDismiss: false, |
|||
containerKey: undefined, |
|||
iconClass: undefined, |
|||
position: { |
|||
top: 'auto', |
|||
right: '30px', |
|||
bottom: '30px', |
|||
left: 'auto' |
|||
} |
|||
}; |
|||
} |
|||
|
|||
// Find existing container or create new one
|
|||
const containerId = globalOptions?.containerKey ? |
|||
`toast-container-${globalOptions.containerKey}` : |
|||
'toast-container'; |
|||
|
|||
this.container = document.getElementById(containerId); |
|||
if (!this.container) { |
|||
this.container = document.createElement('div'); |
|||
this.container.id = containerId; |
|||
this.container.className = 'abp-toast-container'; |
|||
document.body.appendChild(this.container); |
|||
} |
|||
|
|||
this.toasts = []; |
|||
this.lastId = 0; |
|||
|
|||
// Merge user provided global options with defaults
|
|||
this.globalOptions = this.extend({}, AbpToastService.defaultOptions, globalOptions); |
|||
|
|||
this.updateContainerPosition(); |
|||
} |
|||
|
|||
// Deep merge objects
|
|||
AbpToastService.prototype.extend = function(target, ...sources) { |
|||
sources.forEach(source => { |
|||
for (const key in source) { |
|||
if (source[key] && typeof source[key] === 'object') { |
|||
target[key] = this.extend(target[key] || {}, source[key]); |
|||
} else { |
|||
target[key] = source[key]; |
|||
} |
|||
} |
|||
}); |
|||
return target; |
|||
}; |
|||
|
|||
// Update toast container position based on global options
|
|||
AbpToastService.prototype.updateContainerPosition = function() { |
|||
const { position } = this.globalOptions; |
|||
Object.assign(this.container.style, position); |
|||
}; |
|||
|
|||
// Update global options
|
|||
AbpToastService.prototype.setGlobalOptions = function(options) { |
|||
this.globalOptions = this.extend({}, this.globalOptions, options); |
|||
this.updateContainerPosition(); |
|||
}; |
|||
|
|||
// Get icon class based on severity
|
|||
AbpToastService.prototype.getIconClass = function(severity, options) { |
|||
// Use custom icon class if provided
|
|||
if (options.iconClass) { |
|||
return options.iconClass; |
|||
} |
|||
|
|||
const icons = { |
|||
success: 'fa fa-check', |
|||
info: 'fa fa-info-circle', |
|||
warning: 'fa fa-exclamation-triangle', |
|||
error: 'fa fa-exclamation-circle' |
|||
}; |
|||
return icons[severity] || 'fa fa-exclamation-triangle'; |
|||
}; |
|||
|
|||
// Create toast DOM element
|
|||
AbpToastService.prototype.createToastElement = function(message, title, severity, options) { |
|||
const toast = document.createElement('div'); |
|||
toast.className = `abp-toast abp-toast-${severity}`; |
|||
|
|||
const closeButton = options.closable !== false ? |
|||
`<button class="abp-toast-close-button">
|
|||
<i class="fa fa-times" aria-hidden="true"></i> |
|||
</button>` : ''; |
|||
|
|||
const titleHtml = title ? `<div class="abp-toast-title">${title}</div>` : ''; |
|||
|
|||
toast.innerHTML = ` |
|||
<div class="abp-toast-icon"> |
|||
<i class="${this.getIconClass(severity, options)} icon" aria-hidden="true"></i> |
|||
</div> |
|||
<div class="abp-toast-content"> |
|||
${closeButton} |
|||
${titleHtml} |
|||
<p class="abp-toast-message">${message}</p> |
|||
</div>`; |
|||
|
|||
// Add event listeners
|
|||
const closeButtonElement = toast.querySelector('.abp-toast-close-button'); |
|||
if (closeButtonElement) { |
|||
closeButtonElement.addEventListener('click', () => this.remove(toast)); |
|||
} |
|||
|
|||
if (options.tapToDismiss) { |
|||
toast.addEventListener('click', () => this.remove(toast)); |
|||
} |
|||
|
|||
return toast; |
|||
}; |
|||
|
|||
// Show a toast with given options
|
|||
AbpToastService.prototype.show = function(message, title, severity = 'neutral', options = {}) { |
|||
const mergedOptions = this.extend({}, this.globalOptions, options); |
|||
const id = ++this.lastId; |
|||
const toast = this.createToastElement(message, title, severity, mergedOptions); |
|||
|
|||
// Set data attributes for non-object options
|
|||
Object.entries(mergedOptions) |
|||
.filter(([_, value]) => typeof value !== 'object') |
|||
.forEach(([key, value]) => toast.dataset[key] = value); |
|||
|
|||
toast.dataset.id = id; |
|||
this.container.appendChild(toast); |
|||
this.toasts.push(toast); |
|||
|
|||
// Auto remove if not sticky
|
|||
if (!mergedOptions.sticky) { |
|||
setTimeout(() => this.remove(toast), mergedOptions.life); |
|||
} |
|||
|
|||
return id; |
|||
}; |
|||
|
|||
// Remove a toast with animation
|
|||
AbpToastService.prototype.remove = function(toastElement) { |
|||
toastElement.classList.add('toast-removing'); |
|||
setTimeout(() => { |
|||
if (toastElement.parentNode === this.container) { |
|||
this.container.removeChild(toastElement); |
|||
} |
|||
this.toasts = this.toasts.filter(t => t !== toastElement); |
|||
}, 300); // Match animation duration
|
|||
}; |
|||
|
|||
// Convenience methods for different severities
|
|||
AbpToastService.prototype.success = function(message, title, options) { |
|||
return this.show(message, title, 'success', options); |
|||
}; |
|||
|
|||
AbpToastService.prototype.error = function(message, title, options) { |
|||
return this.show(message, title, 'error', options); |
|||
}; |
|||
|
|||
AbpToastService.prototype.info = function(message, title, options) { |
|||
return this.show(message, title, 'info', options); |
|||
}; |
|||
|
|||
AbpToastService.prototype.warning = function(message, title, options) { |
|||
return this.show(message, title, 'warning', options); |
|||
}; |
|||
|
|||
// Clear all toasts
|
|||
AbpToastService.prototype.clear = function(containerKey) { |
|||
if (containerKey) { |
|||
this.toasts = this.toasts.filter(toast => { |
|||
const shouldRemove = toast.dataset.containerKey === containerKey; |
|||
if (shouldRemove) { |
|||
this.remove(toast); |
|||
} |
|||
return !shouldRemove; |
|||
}); |
|||
} else { |
|||
this.toasts.forEach(toast => this.remove(toast)); |
|||
} |
|||
}; |
|||
|
|||
// Static method to set default options for all instances
|
|||
AbpToastService.setDefaultOptions = function(options) { |
|||
AbpToastService.defaultOptions = this.prototype.extend({}, AbpToastService.defaultOptions, options); |
|||
}; |
|||
|
|||
var abp = abp || {}; |
|||
(function () { |
|||
abp.notify.success = function (message, title, options) { |
|||
new AbpToastService().success(message, title, options); |
|||
}; |
|||
|
|||
abp.notify.info = function (message, title, options) { |
|||
new AbpToastService().info(message, title, options); |
|||
}; |
|||
|
|||
abp.notify.warn = function (message, title, options) { |
|||
new AbpToastService().warning(message, title, options); |
|||
}; |
|||
|
|||
abp.notify.error = function (message, title, options) { |
|||
new AbpToastService().error(message, title, options); |
|||
}; |
|||
})(); |
|||
@ -1,34 +0,0 @@ |
|||
var abp = abp || {}; |
|||
(function () { |
|||
|
|||
if (!toastr) { |
|||
return; |
|||
} |
|||
|
|||
/* DEFAULTS *************************************************/ |
|||
|
|||
toastr.options.positionClass = 'toast-bottom-right'; |
|||
|
|||
/* NOTIFICATION *********************************************/ |
|||
|
|||
var showNotification = function (type, message, title, options) { |
|||
toastr[type](message, title, options); |
|||
}; |
|||
|
|||
abp.notify.success = function (message, title, options) { |
|||
showNotification('success', message, title, options); |
|||
}; |
|||
|
|||
abp.notify.info = function (message, title, options) { |
|||
showNotification('info', message, title, options); |
|||
}; |
|||
|
|||
abp.notify.warn = function (message, title, options) { |
|||
showNotification('warning', message, title, options); |
|||
}; |
|||
|
|||
abp.notify.error = function (message, title, options) { |
|||
showNotification('error', message, title, options); |
|||
}; |
|||
|
|||
})(); |
|||
@ -0,0 +1,62 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.EventBus.Local; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Volo.Abp.Uow; |
|||
|
|||
namespace Volo.Abp.EventBus.Distributed; |
|||
|
|||
/// <summary>
|
|||
/// This class is used in unit tests and supports to publish DistributedEventSent and DistributedEventReceived events.
|
|||
/// </summary>
|
|||
public class UnitTestLocalEventBus : LocalEventBus |
|||
{ |
|||
public UnitTestLocalEventBus( |
|||
[NotNull] IOptions<AbpLocalEventBusOptions> options, |
|||
[NotNull] IServiceScopeFactory serviceScopeFactory, |
|||
[NotNull] ICurrentTenant currentTenant, |
|||
[NotNull] IUnitOfWorkManager unitOfWorkManager, |
|||
[NotNull] IEventHandlerInvoker eventHandlerInvoker) |
|||
: base(options, serviceScopeFactory, currentTenant, unitOfWorkManager, eventHandlerInvoker) |
|||
{ |
|||
} |
|||
|
|||
public Func<Type, object, Task> OnEventHandleInvoking { get; set; } |
|||
|
|||
protected async override Task InvokeEventHandlerAsync(IEventHandler eventHandler, object eventData, Type eventType) |
|||
{ |
|||
if (OnEventHandleInvoking != null && eventType != typeof(DistributedEventSent) && eventType != typeof(DistributedEventReceived)) |
|||
{ |
|||
await OnEventHandleInvoking(eventType, eventData); |
|||
} |
|||
|
|||
await base.InvokeEventHandlerAsync(eventHandler, eventData, eventType); |
|||
} |
|||
|
|||
public Func<Type, object, Task> OnPublishing { get; set; } |
|||
|
|||
public async override Task PublishAsync( |
|||
Type eventType, |
|||
object eventData, |
|||
bool onUnitOfWorkComplete = true) |
|||
{ |
|||
if (onUnitOfWorkComplete && UnitOfWorkManager.Current != null) |
|||
{ |
|||
AddToUnitOfWork( |
|||
UnitOfWorkManager.Current, |
|||
new UnitOfWorkEventRecord(eventType, eventData, EventOrderGenerator.GetNext()) |
|||
); |
|||
return; |
|||
} |
|||
|
|||
if (OnPublishing != null && eventType != typeof(DistributedEventSent) && eventType != typeof(DistributedEventReceived)) |
|||
{ |
|||
await OnPublishing(eventType, eventData); |
|||
} |
|||
|
|||
await PublishToEventBusAsync(eventType, eventData); |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
using System; |
|||
using System.Text.RegularExpressions; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.AuditLogging; |
|||
|
|||
public class AuditLogEntityTypeFullNameConverter : ITransientDependency |
|||
{ |
|||
public virtual string Convert(string typeFullName) |
|||
{ |
|||
var genericType = Regex.Match(typeFullName, @"(.+?)`1\[\["); |
|||
if (!genericType.Success) |
|||
{ |
|||
return ReplaceGenericSymbol(typeFullName); |
|||
} |
|||
|
|||
var type = Regex.Match(typeFullName, @"`1\[\[(.+?), "); |
|||
if (!type.Success) |
|||
{ |
|||
return typeFullName; |
|||
} |
|||
|
|||
if (type.Groups[1].Value.Contains("System.Nullable`1[[")) |
|||
{ |
|||
return genericType.Groups[1].Value + "<" + type.Groups[1].Value.Replace("System.Nullable`1[[", "") + "?>"; |
|||
} |
|||
|
|||
return genericType.Groups[1].Value.Contains("System.Nullable") |
|||
? type.Groups[1].Value + "?" |
|||
: genericType.Groups[1].Value + "<" + ReplaceGenericSymbol(type.Groups[1].Value) + ">"; |
|||
} |
|||
|
|||
protected virtual string ReplaceGenericSymbol(string typeFullName) |
|||
{ |
|||
return typeFullName.Contains("`1+") |
|||
? typeFullName.Substring(0, typeFullName.IndexOf("[[", StringComparison.Ordinal)).Replace("`1+", ".") |
|||
: typeFullName; |
|||
} |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
namespace Volo.Abp.AuditLogging.EntityFrameworkCore; |
|||
|
|||
public class AuditLogEntityTypeFullNameConverter_Tests : AuditLogEntityTypeFullNameConverter_Tests<AbpAuditLoggingEntityFrameworkCoreTestModule> |
|||
{ |
|||
|
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.AuditLogging.MongoDB; |
|||
|
|||
[Collection(MongoTestCollection.Name)] |
|||
public class AuditLogEntityTypeFullNameConverter_Tests : AuditLogEntityTypeFullNameConverter_Tests<AbpAuditLoggingMongoDbTestModule> |
|||
{ |
|||
|
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Collections.ObjectModel; |
|||
using Shouldly; |
|||
using Volo.Abp.Modularity; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.AuditLogging; |
|||
|
|||
public abstract class AuditLogEntityTypeFullNameConverter_Tests<TStartupModule> : AuditLoggingTestBase<TStartupModule> |
|||
where TStartupModule : IAbpModule |
|||
{ |
|||
private readonly AuditLogEntityTypeFullNameConverter _typeFullNameConverter; |
|||
|
|||
protected AuditLogEntityTypeFullNameConverter_Tests() |
|||
{ |
|||
_typeFullNameConverter = GetRequiredService<AuditLogEntityTypeFullNameConverter>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void AuditLogEntityTypeFullNameConverter_Test() |
|||
{ |
|||
_typeFullNameConverter.Convert("MyType").ShouldBe("MyType"); |
|||
|
|||
_typeFullNameConverter.Convert(typeof(string).FullName!).ShouldBe("System.String"); |
|||
_typeFullNameConverter.Convert(typeof(Guid).FullName!).ShouldBe("System.Guid"); |
|||
_typeFullNameConverter.Convert(typeof(Guid?).FullName!).ShouldBe("System.Guid?"); |
|||
_typeFullNameConverter.Convert(typeof(int).FullName!).ShouldBe("System.Int32"); |
|||
_typeFullNameConverter.Convert(typeof(long?).FullName!).ShouldBe("System.Int64?"); |
|||
_typeFullNameConverter.Convert(typeof(MyClass).FullName!).ShouldBe("Volo.Abp.AuditLogging.AuditLogEntityTypeFullNameConverter_Tests.MyClass"); |
|||
|
|||
_typeFullNameConverter.Convert(typeof(ICollection<string>).FullName!).ShouldBe($"System.Collections.Generic.ICollection<System.String>"); |
|||
_typeFullNameConverter.Convert(typeof(Collection<int>).FullName!).ShouldBe($"System.Collections.ObjectModel.Collection<System.Int32>"); |
|||
_typeFullNameConverter.Convert(typeof(List<Guid>).FullName!).ShouldBe($"System.Collections.Generic.List<System.Guid>"); |
|||
_typeFullNameConverter.Convert(typeof(List<MyClass>).FullName!).ShouldBe($"System.Collections.Generic.List<Volo.Abp.AuditLogging.AuditLogEntityTypeFullNameConverter_Tests.MyClass>"); |
|||
|
|||
_typeFullNameConverter.Convert(typeof(ICollection<long?>).FullName!).ShouldBe($"System.Collections.Generic.ICollection<System.Int64?>"); |
|||
_typeFullNameConverter.Convert(typeof(Collection<int?>).FullName!).ShouldBe($"System.Collections.ObjectModel.Collection<System.Int32?>"); |
|||
_typeFullNameConverter.Convert(typeof(List<Guid?>).FullName!).ShouldBe($"System.Collections.Generic.List<System.Guid?>"); |
|||
} |
|||
|
|||
public class MyClass |
|||
{ |
|||
|
|||
} |
|||
} |
|||