diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json
index b14d1ffa70..92d7450319 100644
--- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json
@@ -601,7 +601,7 @@
"PaymentSucceed_ViewOrganization": "Click here to view organization",
"Purchase_TotalAnnualPrice": "TOTAL (annual fee)",
"Purchase_TrainingPrice": "Training Price",
- "Purchase_OnboardingTraining": "ABP Onboarding & Web Application Development Live Training",
+ "Purchase_OnboardingTraining": "Onboarding & Web Application Development Live Training",
"TotalDeveloperPrice": "Total Developer Price",
"Purchase_PricePerDeveloper": "{0} {1} per developer",
"Purchase_IncludedDeveloperInfo": "{0} {1} included.",
@@ -978,10 +978,10 @@
"Purchase_DevelopersAlreadyIncluded": "{0} developers already included",
"1Year": "1 year",
"{0}Years": "{0} years",
- "1YearLicense": "1 year license",
- "{0}YearsLicense": "{0} years license",
- "1AdditionalDeveloper": "1 additional developer",
- "{0}AdditionalDevelopers": "{0} additional developers",
+ "1YearLicense": "1 Year License",
+ "{0}YearsLicense": "{0} Years License",
+ "1AdditionalDeveloper": "1 Additional Developer",
+ "{0}AdditionalDevelopers": "{0} Additional Developers",
"Discount": "Discount ({0}%)",
"Summary": "Summary",
"TrainingPack": "Training pack",
@@ -1081,6 +1081,10 @@
"CampaignInfo": "Buy a new license or renew your existing license and get an additional 2 months at no additional cost! This offer is valid for all license plans. Ensure you take advantage of this limited-time promotion to expand your access to premium features and upgrades.",
"HurryUpLastDay": "Hurry Up! Last Day: {0}",
"CreatingCRUDPagesWithABPSuite": "Creating CRUD pages with ABP Suite",
- "Testimonials": "Testimonials"
+ "Testimonials": "Testimonials",
+ "MultipleYearDiscount": "Multiple Year Discount",
+ "CampaignDiscountText": "Black Friday Discount",
+ "CampaignDiscountName": "Black Friday",
+ "CampaignName:BlackFriday": "Black Friday"
}
}
diff --git a/docs/en/Community-Articles/2023-04-15-Converting-Create-Edit-Modal-To-Page/POST.md b/docs/en/Community-Articles/2023-04-15-Converting-Create-Edit-Modal-To-Page/POST.md
index a8f1aeadfa..73d2e6bad5 100644
--- a/docs/en/Community-Articles/2023-04-15-Converting-Create-Edit-Modal-To-Page/POST.md
+++ b/docs/en/Community-Articles/2023-04-15-Converting-Create-Edit-Modal-To-Page/POST.md
@@ -315,7 +315,7 @@ import { CreateBookComponent } from './create-book/create-book.component';
import { EditBookComponent } from './edit-book/edit-book.component';
const routes: Routes = [
- { path: '', component: BookComponent, canActivate: [AuthGuard, PermissionGuard] },
+ { path: '', component: BookComponent, canActivate: [authGuard, permissionGuard] },
{ path: 'create', component: CreateBookComponent },
{ path: 'edit/:id', component: EditBookComponent },
];
diff --git a/docs/en/Community-Articles/2023-10-30-Enhancements-to-JSON-column-mapping/POST.md b/docs/en/Community-Articles/2023-10-30-Enhancements-to-JSON-column-mapping/POST.md
new file mode 100644
index 0000000000..db8f72ca17
--- /dev/null
+++ b/docs/en/Community-Articles/2023-10-30-Enhancements-to-JSON-column-mapping/POST.md
@@ -0,0 +1,124 @@
+# EF Core 8 - Enhancements to JSON column mapping
+
+In this article, we will examine the enhancements introduced in EF Core 8 for the JSON column feature, building upon the foundation laid by [JSON columns in Entity Framework Core 7](https://community.abp.io/posts/json-columns-in-entity-framework-core-7-cjaom76j).
+
+## The entity classes we will be using in the article
+
+```csharp
+public class Person
+{
+ public int Id { get; set; }
+ [Required]
+ public string Name { get; set; }
+ [Required]
+ public ContactDetails ContactDetails { get; set; }
+}
+
+public class ContactDetails
+{
+ public List
Addresses { get; set; } = new();
+ public string? Phone { get; set; }
+}
+
+public class Address
+{
+ public Address(string street, string city, string postcode, string country)
+ {
+ Street = street;
+ City = city;
+ Postcode = postcode;
+ Country = country;
+ }
+
+ public string Street { get; set; }
+ public string City { get; set; }
+ public string Postcode { get; set; }
+ public string Country { get; set; }
+ public bool IsMainAddress { get; set; }
+}
+```
+
+## The DbContext class we will be using in the article
+
+```csharp
+public class AppDbContext : DbContext
+{
+ public DbSet Persons { get; set; } = null!;
+
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ {
+#if SQLSERVER
+ optionsBuilder.UseSqlServer("Server=localhost;Database=EfCore8Json;Trusted_Connection=True;TrustServerCertificate=True");
+#elif SQLITE
+ optionsBuilder.UseSqlite("Data Source=EfCore8Json.db");
+#endif
+ base.OnConfiguring(optionsBuilder);
+ }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ modelBuilder.Entity(b =>
+ {
+ b.ToTable("Persons");
+ b.HasKey(x => x.Id);
+ b.Property(x => x.Name).IsRequired();
+ b.OwnsOne(x => x.ContactDetails, cb =>
+ {
+ cb.ToJson();
+ cb.Property(x => x.Phone);
+ cb.OwnsMany(x => x.Addresses);
+ });
+ });
+
+ base.OnModelCreating(modelBuilder);
+ }
+}
+```
+
+## Translate element access into JSON arrays
+
+EF Core 8 supports indexing in JSON arrays when executing queries. For example, the following query returns individuals whose first address is the main address in the database:
+
+```csharp
+var query = dbContext.Persons
+ .Select(x => x.ContactDetails.Addresses[0])
+ .Where(x => x.IsMainAddress == true)
+ .ToListAsync();
+```
+
+The generated SQL query is as follows when using SQL Server:
+
+```sql
+SELECT JSON_QUERY([p].[ContactDetails], '$.Addresses[0]'), [p].[Id]
+FROM [Persons] AS [p]
+WHERE CAST(JSON_VALUE([p].[ContactDetails], '$.Addresses[0].IsMainAddress') AS bit) = CAST(1 AS bit)
+```
+
+> Note: If you attempt to access an index that is outside of the array, it will return null.
+
+## JSON Columns for SQLite
+
+In EF Core 7, JSON column mapping was supported for Azure SQL/SQL Server. In EF Core 8, this support has been extended to include SQLite as well.
+
+### Queries into JSON columns
+
+The following query returns individuals whose first address is the main address in the database:
+
+```csharp
+var query = dbContext.Persons
+ .Select(x => x.ContactDetails.Addresses[0])
+ .Where(x => x.IsMainAddress == true)
+ .ToListAsync();
+```
+
+The generated SQL query is as follows when using SQLite:
+
+```sql
+SELECT "p"."ContactDetails" ->> '$.Addresses[0]', "p"."Id"
+FROM "Persons" AS "p"
+WHERE "p"."ContactDetails" ->> '$.Addresses[0].IsMainAddress' = 0
+```
+
+## References
+
+- [EF Core 8 - Enhancements to JSON column mapping](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#enhancements-to-json-column-mapping)
\ No newline at end of file
diff --git a/docs/en/Community-Articles/2023-10-31-NET-8-ASP-NET-Core-Minimal-APIs/POST.md b/docs/en/Community-Articles/2023-10-31-NET-8-ASP-NET-Core-Minimal-APIs/POST.md
new file mode 100644
index 0000000000..03ca906884
--- /dev/null
+++ b/docs/en/Community-Articles/2023-10-31-NET-8-ASP-NET-Core-Minimal-APIs/POST.md
@@ -0,0 +1,156 @@
+# New Minimal APIs features in ASP.NET Core 8.0
+
+In this article, we will see the new features of Minimal APIs in ASP.NET Core 8.0.
+
+## Binding to forms
+
+We can bind to forms using the [FromForm] attribute. Let's see an example:
+
+```csharp
+app.MapPost("/books", async ([FromForm] string name,
+ [FromForm] BookType bookType, IFormFile? cover, BookDb db) =>
+{
+ var book = new Book
+ {
+ Name = name,
+ BookType = bookType
+ };
+
+ if (cover is not null)
+ {
+ var coverName = Path.GetRandomFileName();
+
+ using var stream = File.Create(Path.Combine("wwwroot", coverName));
+ await cover.CopyToAsync(stream);
+ book.Cover = coverName;
+ }
+
+ db.Books.Add(book);
+ await db.SaveChangesAsync();
+
+ return Results.Ok();
+});
+```
+
+Another way is using the [AsParameters] attribute, the following code binds from form values to properties of the `NewBookRequest` record struct:
+
+```csharp
+public record NewBookRequest([FromForm] string Name, [FromForm] BookType BookType, IFormFile? Cover);
+
+app.MapPost("/books", async ([AsParameters] NewBookRequest request, BookDb db) =>
+{
+ var book = new Book
+ {
+ Name = request.Name,
+ BookType = request.BookType
+ };
+
+ if (request.Cover is not null)
+ {
+ var coverName = Path.GetRandomFileName();
+
+ using var stream = File.Create(Path.Combine("wwwroot", coverName));
+ await request.Cover.CopyToAsync(stream);
+ book.Cover = coverName;
+ }
+
+ db.Books.Add(book);
+ await db.SaveChangesAsync();
+
+ return Results.Ok();
+});
+```
+
+## Antiforgery
+
+ASP.NET Core 8.0 adds support for antiforgery tokens. We can call the `AddAntiforgery` method to register the antiforgery services and `WebApplicationBuilder` will automatically add the antiforgery middleware to the pipeline:
+
+```csharp
+var builder = WebApplication.CreateBuilder();
+
+builder.Services.AddAntiforgery();
+
+var app = builder.Build();
+
+// Implicitly added by WebApplicationBuilder if AddAntiforgery is called.
+// app.UseAntiforgery();
+
+app.MapGet("/", () => "Hello World!");
+
+app.Run();
+```
+
+Example of using antiforgery tokens:
+
+```csharp
+
+// Use the antiforgery service to generate tokens.
+app.MapGet("/", (HttpContext context, IAntiforgery antiforgery) =>
+{
+ var token = antiforgery.GetAndStoreTokens(context);
+ return Results.Content(...., "text/html");
+});
+
+// It will automatically validate the token.
+app.MapPost("/todo", ([FromForm] Todo todo) => Results.Ok(todo));
+
+// Disable antiforgery validation for this endpoint.
+app.MapPost("/todo2", ([FromForm] Todo todo) => Results.Ok(todo))
+ .DisableAntiforgery();
+```
+
+
+## Native AOT
+
+ASP.NET Core 8.0 adds support for Native AOT.
+
+You can add `PublishAot` to the project file to enable Native AOT:
+
+```xml
+
+ true
+
+```
+
+### Web API (native AOT) template
+
+You can use the `dotnet new webapiaot` command to create a new Minimal APIs project with Native AOT enabled.
+
+```diff
++using System.Text.Json.Serialization;
+
+-var builder = WebApplication.CreateBuilder();
++var builder = WebApplication.CreateSlimBuilder(args);
+
++builder.Services.ConfigureHttpJsonOptions(options =>
++{
++ options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
++});
+
+var app = builder.Build();
+
+var sampleTodos = TodoGenerator.GenerateTodos().ToArray();
+
+var todosApi = app.MapGroup("/todos");
+todosApi.MapGet("/", () => sampleTodos);
+todosApi.MapGet("/{id}", (int id) =>
+ sampleTodos.FirstOrDefault(a => a.Id == id) is { } todo
+ ? Results.Ok(todo)
+ : Results.NotFound());
+
+app.Run();
+
++[JsonSerializable(typeof(Todo[]))]
++internal partial class AppJsonSerializerContext : JsonSerializerContext
++{
++
++}
+```
+
+* Reflection isn't supported in native AOT, you must use the `JsonSerializable` attribute to specify the types that you want to serialize/deserialize.
+
+## References
+
+* https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-8.0?view=aspnetcore-8.0#minimal-apis
+* https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis/parameter-binding?view=aspnetcore-8.0#explicit-binding-from-form-values
+* https://learn.microsoft.com/en-us/aspnet/core/fundamentals/native-aot?view=aspnetcore-8.0
diff --git a/docs/en/Community-Articles/2023-11-06-EF-Core_Hierarchy-Id/POST.md b/docs/en/Community-Articles/2023-11-06-EF-Core_Hierarchy-Id/POST.md
new file mode 100644
index 0000000000..9255c39a5a
--- /dev/null
+++ b/docs/en/Community-Articles/2023-11-06-EF-Core_Hierarchy-Id/POST.md
@@ -0,0 +1,79 @@
+# HierarchyId Support in Entity Framework Core
+
+Entity Framework Core has official support for HierarchyId, which allows you to store and query hierarchical data in SQL Server databases. Hierarchical data is a common data structure found in many applications. Whether you are dealing with organizational structures, product categories, or threaded discussions, handling hierarchies efficiently is crucial. In this blog post, we will explore how to manage hierarchical data using Entity Framework Core (EF Core) in combination with HierarchyId.
+
+## How to use HierarchyId in EF Core
+
+To use HierarchyId in EF Core, you need to install the [Microsoft.EntityFrameworkCore.SqlServer.HierarchyId](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.SqlServer.HierarchyId/) NuGet package. This package contains query and update support for HierarchyId.
+
+To configure EF Core to use HierarchyId, you need to call the `UseHierarchyId` method when configuring the SQL Server provider.
+
+```csharp
+options.UseSqlServer(
+ connectionString,
+ x => x.UseHierarchyId());
+```
+
+## Modeling Hierarchies
+
+The `HierarchyId` type can be used for properties of an entity type. For example, assume we want to model the personnel in an organization. Each person has a name and a manager. The manager is also a person. We can model this using the following entity type:
+
+```csharp
+public class Person
+{
+ public int Id { get; set; }
+ public string Name { get; set; }
+ public HierarchyId Manager { get; set; }
+}
+```
+
+Each person can be traced from the patriarch down the tree using its `Manager` property. SQL Server uses a compact binary format for these paths, but it is common to parse to and from a human-readable string representation when working with code. In this representation, the position at each level is separated by a `/` character.
+
+
+
+## Querying Hierarchies
+
+HierarchyId exposes several methods that can be used in LINQ queries.
+
+* `GetAncestor(int level)` returns the ancestor at the specified level.
+
+* `GetDescendant(HierarchyId? child1, HierarchyId? child2)` returns the descendant at the specified level.
+
+* `GetLevel()` returns the level of the node in the hierarchy.
+
+* `GetReparentedValue(HierarchyId? oldRoot, HierarchyId? newRoot)` returns the node with a new parent.
+
+* `IsDescendantOf(HierarchyId? parent)` returns whether the node is a descendant of the specified node.
+
+Example:
+
+```csharp
+// Get entities at a given level in the tree
+var generation = await context.Persons.Where(x => x.Manager.GetLevel() == level).ToListAsync();
+
+// Get the direct ancestor of an entity
+var parent = await context.Persons.Where(x => x.Manager.GetAncestor(1) == manager).SingleAsync();
+```
+
+For example, your company may want to change the manager of an organizational unit. To do this, you can use the `GetReparentedValue` method to update the manager for all descendants of the organizational unit.
+
+```csharp
+var newManager = await context.Persons.SingleAsync(x => x.Id == newManagerId);
+
+var descendants = await context.Persons
+ .Where(x => x.Manager.IsDescendantOf(oldManager))
+ .ToListAsync();
+
+foreach (var descendant in descendants)
+{
+ descendant.Manager = descendant.Manager.GetReparentedValue(oldManager.Manager, newManager.Manager);
+}
+
+await context.SaveChangesAsync();
+```
+
+## References
+
+For more information about hierarchy id, see the following resource:
+
+https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#hierarchyid-in-net-and-ef-core
diff --git a/docs/en/Community-Articles/2023-11-06-EF-Core_Hierarchy-Id/hierarchy-tree.png b/docs/en/Community-Articles/2023-11-06-EF-Core_Hierarchy-Id/hierarchy-tree.png
new file mode 100644
index 0000000000..e22ed9781c
Binary files /dev/null and b/docs/en/Community-Articles/2023-11-06-EF-Core_Hierarchy-Id/hierarchy-tree.png differ
diff --git a/docs/en/Community-Articles/2023-11-97-AOT-Compilation/Post.md b/docs/en/Community-Articles/2023-11-97-AOT-Compilation/Post.md
new file mode 100644
index 0000000000..2b994b59c3
--- /dev/null
+++ b/docs/en/Community-Articles/2023-11-97-AOT-Compilation/Post.md
@@ -0,0 +1,75 @@
+# Native AOT Compilation in .NET 8
+Native AOT (Ahead-of-Time) compilation is a feature that allows developers to create a self-contained app compiled to native code that can run on machines without the .NET runtime installed. It results in benefits such as minimized disk footprint, reduced executable size, reduced startup time, and reduced memory demand.
+
+Native AOT compilation isn't a new feature in .NET 8. It's first introduced in .NET 7.
+
+
+Differences between the AOT Compilation of .NET 7 and .NET 8 are:
+
+
+- **System.Text.Json improvements**: .NET 8 adds support for more types, source generation, interface hierarchies, naming policies, read-only properties, and more.
+- **New types for performance**: .NET 8 introduces new types such as FrozenDictionary, FrozenSet, SearchValues, CompositeFormat, TimeProvider, and ITimer to improve the app performance.
+- **System.Numerics and System.Runtime.Intrinsics enhancements**: .NET 8 adds support for Vector512, AVX-512, IUtf8SpanFormattable, Lerp, and more.
+- **System.ComponentModel.DataAnnotations additions**: .NET 8 adds new data validation attributes for cloud-native services and a new ValidateOptionsResultBuilder type.
+- **Hosted services lifecycle methods**: .NET 8 adds new methods such as StartAsync, StopAsync, StartBackgroundAsync, and StopBackgroundAsync for hosted services.
+
+It's important to note that not all features in ASP.NET Core are currently compatible with native AOT. For more information, see [Native AOT deployment overview](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/).
+
+## How to use Native AOT Compilation in .NET 8
+
+You can add `true` in your project .csproj file to enable Native AOT Compilation.
+
+ - For the new projects, you can create them with the `--aot` parameter. Example: `dotnet new console --aot`.
+
+By default, the compiler chooses a blended approach code optimization but you can specify an optimization preference inside your .csproj file. You can choose **size** or **speed** according your requirements.
+
+```xml
+Size
+```
+
+or
+
+```xml
+Speed
+```
+
+### Results
+
+I have created a simple console application to test the Native AOT Compilation. I have used a simple console application that writes "Hello World!" to the console 100 times. I have tested the application with different optimization preferences. I have used the following results:
+
+
+| | Size | Speed |
+| --- | --- | --- |
+| .NET 8 _(Self-Contained, Single File)_ | 65938 kb | 00.0051806 ~5ms |
+| .NET 7 AOT (default) | 4452 kb | 00.0029823 ~2ms |
+| .NET 8 AOT (default) | 1242 kb | 00.0028638 ~2ms |
+| AOT (Speed)| 1280 kb | 00.0023838 ~2ms |
+| AOT (Size) | 1111 kb | 00.0025145 ~2ms |
+
+Most of existing libraries don't support AOT compilation yet, so I couldn't use [BenchmarkDotnet](https://github.com/dotnet/BenchmarkDotNet) to measure the performance. I have used [Stopwatch](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.stopwatch?view=net-8.0) to measure the performance. So the performance results may not be accurate but gives insight about the performance difference.
+
+## AOT Support in MAUI
+You can now use Native AOT Compilation on iOS-like target frameworks in .NET MAUI. You can enable AOT compilation with the exact same method by adding `true` to your project .csproj file. According to the dotnet team, apps sizes reduced by 35% and startup times reduced by 28% with AOT compilation. And runtime performance is also improved by 50%.
+
+But there are some limitations in MAUI AOT Compilation. A lot of libraries still don't support AOT compilation and some of platform-specific feaetures may not work at the moment.
+
+## When to use Native AOT Compilation?
+
+Native AOT Compilation is beneficial when you need to optimize your .NET application for speed and size. It's particularly useful for applications that require quick startup times and efficient runtime performance, such as mobile apps or high-performance computing applications.
+
+However, due to its current limitations, it might not be suitable for all projects. If your project relies heavily on libraries that do not support AOT compilation, or if it uses platform-specific features that are not yet compatible with AOT, you might want to hold off on using Native AOT Compilation until further improvements are made.
+
+Always consider the specific needs and constraints of your project before deciding to use Native AOT Compilation.
+
+## Conclusion
+
+Native AOT Compilation is a great feature that improves the performance of .NET applications. It's still in early-stages and not all libraries support it yet. But it's a great beginning for the future of .NET 🚀
+
+
+## Links
+- Native AOT deployment overview - .NET | Microsoft Learn. https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/.
+-
+- Optimize AOT deployments https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/optimizing
+-
+- What's new in .NET 8 | Microsoft Learn. https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8.
+
diff --git a/docs/en/Startup-Templates/Application.md b/docs/en/Startup-Templates/Application.md
index 0c5e9c4c38..fdb7569ca0 100644
--- a/docs/en/Startup-Templates/Application.md
+++ b/docs/en/Startup-Templates/Application.md
@@ -316,7 +316,7 @@ You should add `routes` property in the `data` object to add a link on the menu
{
path: 'dashboard',
loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule),
- canActivate: [AuthGuard, PermissionGuard],
+ canActivate: [authGuard, permissionGuard],
data: {
routes: {
name: 'ProjectName::Menu:Dashboard',
@@ -328,7 +328,7 @@ You should add `routes` property in the `data` object to add a link on the menu
}
```
In the above example;
-* If the user is not logged in, AuthGuard blocks access and redirects to the login page.
+* If the user is not logged in, authGuard blocks access and redirects to the login page.
* PermissionGuard checks the user's permission with the `requiredPolicy` property of the `routes` object. If the user is not authorized to access the page, the 403 page appears.
* The `name` property of `routes` is the menu link label. A localization key can be defined.
* The `iconClass` property of the `routes` object is the menu link icon class.
diff --git a/docs/en/Tutorials/Part-5.md b/docs/en/Tutorials/Part-5.md
index f33a41f392..f306b7f4a2 100644
--- a/docs/en/Tutorials/Part-5.md
+++ b/docs/en/Tutorials/Part-5.md
@@ -323,11 +323,11 @@ Open the `/src/app/book/book-routing.module.ts` and replace with the following c
````js
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
-import { AuthGuard, PermissionGuard } from '@abp/ng.core';
+import { authGuard, permissionGuard } from '@abp/ng.core';
import { BookComponent } from './book.component';
const routes: Routes = [
- { path: '', component: BookComponent, canActivate: [AuthGuard, PermissionGuard] },
+ { path: '', component: BookComponent, canActivate: [authGuard, permissionGuard] },
];
@NgModule({
@@ -337,8 +337,8 @@ const routes: Routes = [
export class BookRoutingModule {}
````
-* Imported `AuthGuard` and `PermissionGuard` from the `@abp/ng.core`.
-* Added `canActivate: [AuthGuard, PermissionGuard]` to the route definition.
+* Imported `authGuard` and `permissionGuard` from the `@abp/ng.core`.
+* Added `canActivate: [authGuard, permissionGuard]` to the route definition.
Open the `/src/app/route.provider.ts` and add `requiredPolicy: 'BookStore.Books'` to the `/books` route. The `/books` route block should be following:
diff --git a/docs/en/UI/Angular/Entity-Action-Extensions.md b/docs/en/UI/Angular/Entity-Action-Extensions.md
index 3ab035b04a..d93b2045f6 100644
--- a/docs/en/UI/Angular/Entity-Action-Extensions.md
+++ b/docs/en/UI/Angular/Entity-Action-Extensions.md
@@ -16,19 +16,16 @@ In this example, we will add a "Click Me!" action and alert the current row's `u
The following code prepares a constant named `identityEntityActionContributors`, ready to be imported and used in your root module:
-```js
+```ts
// src/app/entity-action-contributors.ts
-import {
- eIdentityComponents,
- IdentityEntityActionContributors,
- IdentityUserDto,
-} from '@abp/ng.identity';
+import { eIdentityComponents, IdentityEntityActionContributors } from '@abp/ng.identity';
+import { IdentityUserDto } from '@abp/ng.identity/proxy';
import { EntityAction, EntityActionList } from '@abp/ng.theme.shared/extensions';
const alertUserName = new EntityAction({
text: 'Click Me!',
- action: data => {
+ action: (data) => {
// Replace alert with your custom code
alert(data.record.userName);
},
diff --git a/docs/en/UI/Angular/OAuth-Module.md b/docs/en/UI/Angular/OAuth-Module.md
index e7a62d1d2e..b747c5221d 100644
--- a/docs/en/UI/Angular/OAuth-Module.md
+++ b/docs/en/UI/Angular/OAuth-Module.md
@@ -6,7 +6,6 @@ If your app is version 7.0 or higher, you should include "AbpOAuthModule.forRoot
Those abstractions can be found in the @abp/ng-core packages.
- `AuthService` (the class that implements the IAuthService interface).
- `NAVIGATE_TO_MANAGE_PROFILE` Inject token.
-- `AuthGuard` (the class that implements the IAuthGuard interface).
- `ApiInterceptor` (the class that implements the IApiInterceptor interface).
Those base classes are overridden by the "AbpOAuthModule" for oAuth. There are also three functions provided with AbpOAuthModule.
diff --git a/docs/en/UI/Angular/Permission-Management.md b/docs/en/UI/Angular/Permission-Management.md
index dd373d6274..63d919f599 100644
--- a/docs/en/UI/Angular/Permission-Management.md
+++ b/docs/en/UI/Angular/Permission-Management.md
@@ -54,20 +54,20 @@ As shown above you can remove elements from DOM with `abpPermission` structural
## Permission Guard
-You can use `PermissionGuard` if you want to control authenticated user's permission to access to the route during navigation.
+You can use `permissionGuard` if you want to control authenticated user's permission to access to the route during navigation.
* Import the PermissionGuard from @abp/ng.core.
-* Add `canActivate: [PermissionGuard]` to your route object.
+* Add `canActivate: [permissionGuard]` to your route object.
* Add `requiredPolicy` to the `data` property of your route in your routing module.
```js
-import { PermissionGuard } from '@abp/ng.core';
+import { permissionGuard } from '@abp/ng.core';
// ...
const routes: Routes = [
{
path: 'path',
component: YourComponent,
- canActivate: [PermissionGuard],
+ canActivate: [permissionGuard],
data: {
requiredPolicy: 'YourProjectName.YourComponent', // policy key for your component
},
diff --git a/docs/en/UI/AspNetCore/Bundling-Minification.md b/docs/en/UI/AspNetCore/Bundling-Minification.md
index dd2282c883..6164d00c7d 100644
--- a/docs/en/UI/AspNetCore/Bundling-Minification.md
+++ b/docs/en/UI/AspNetCore/Bundling-Minification.md
@@ -382,6 +382,83 @@ Configure(options =>
````
+### External/CDN file Support
+
+The bundling system automatically recognizes the external/CDN files and adds them to the page without any change.
+
+#### Using External/CDN files in `AbpBundlingOptions`
+
+````csharp
+Configure(options =>
+{
+ options.StyleBundles
+ .Add("MyStyleBundle", configuration =>
+ {
+ configuration
+ .AddFiles("/styles/my-style1.css")
+ .AddFiles("/styles/my-style2.css")
+ .AddFiles("https://cdn.abp.io/bootstrap.css")
+ .AddFiles("/styles/my-style3.css")
+ .AddFiles("/styles/my-style4.css");
+ });
+
+ options.ScriptBundles
+ .Add("MyScriptBundle", configuration =>
+ {
+ configuration
+ .AddFiles("/scripts/my-script1.js")
+ .AddFiles("/scripts/my-script2.js")
+ .AddFiles("https://cdn.abp.io/bootstrap.js")
+ .AddFiles("/scripts/my-script3.js")
+ .AddFiles("/scripts/my-script4.js");
+ });
+});
+````
+
+**Output HTML:**
+
+````html
+
+
+
+
+
+
+
+````
+
+#### Using External/CDN files in Tag Helpers.
+
+````html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+````
+
+**Output HTML:**
+
+````html
+
+
+
+
+
+
+
+````
+
## Themes
Themes uses the standard package contributors to add library resources to page layouts. Themes may also define some standard/global bundles, so any module can contribute to these standard/global bundles. See the [theming documentation](Theming.md) for more.
diff --git a/docs/zh-Hans/Startup-Templates/Application.md b/docs/zh-Hans/Startup-Templates/Application.md
index 91d7308226..d6733b8633 100644
--- a/docs/zh-Hans/Startup-Templates/Application.md
+++ b/docs/zh-Hans/Startup-Templates/Application.md
@@ -302,7 +302,7 @@ ABP 配置模块也已经导入到 `AppModule` 中, 以满足可延迟加载 ABP
{
path: 'dashboard',
loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule),
- canActivate: [AuthGuard, PermissionGuard],
+ canActivate: [authGuard, permissionGuard],
data: {
routes: {
name: 'ProjectName::Menu:Dashboard',
@@ -315,7 +315,7 @@ ABP 配置模块也已经导入到 `AppModule` 中, 以满足可延迟加载 ABP
```
在上面的例子中;
* 如果用户没有登录, AuthGuard 会阻塞访问并重定向到登录页面.
-* PermissionGuard 使用 `rotues` 对象的 `requiredPolicy` 属性检查用户的权限. 如果用户未被授权访问该页, 则显示403页.
+* permissionGuard 使用 `rotues` 对象的 `requiredPolicy` 属性检查用户的权限. 如果用户未被授权访问该页, 则显示403页.
* `routes` 的 `name` 属性是菜单链接标签. 可以定义本地化 key.
* `routes` 对象的 `iconClass` 属性是菜单链接图标类.
* `routes` 对象的 `requiredPolicy` 属性是访问页面所需的策略 key.
diff --git a/docs/zh-Hans/Tutorials/Part-5.md b/docs/zh-Hans/Tutorials/Part-5.md
index 5bdad55c1c..ec308f3ee7 100644
--- a/docs/zh-Hans/Tutorials/Part-5.md
+++ b/docs/zh-Hans/Tutorials/Part-5.md
@@ -389,11 +389,11 @@ UI的第一步是防止未认证用户看见"图书"菜单项并进入图书管
````js
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
-import { AuthGuard, PermissionGuard } from '@abp/ng.core';
+import { authGuard, permissionGuard } from '@abp/ng.core';
import { BookComponent } from './book.component';
const routes: Routes = [
- { path: '', component: BookComponent, canActivate: [AuthGuard, PermissionGuard] },
+ { path: '', component: BookComponent, canActivate: [authGuard, permissionGuard] },
];
@NgModule({
@@ -403,8 +403,8 @@ const routes: Routes = [
export class BookRoutingModule {}
````
-* 从 `@abp/ng.core` 引入 `AuthGuard` 和 `PermissionGuard`.
-* 在路由定义中添加 `canActivate: [AuthGuard, PermissionGuard]`.
+* 从 `@abp/ng.core` 引入 `authGuard` 和 `permissionGuard`.
+* 在路由定义中添加 `canActivate: [authGuard, permissionGuard]`.
打开 `/src/app/route.provider.ts`, 在 `/books` 路由中添加 `requiredPolicy: 'BookStore.Books'`. `/books` 路由应该如以下配置:
diff --git a/docs/zh-Hans/UI/Angular/Permission-Management.md b/docs/zh-Hans/UI/Angular/Permission-Management.md
index 08496f03fe..05e3dacbf1 100644
--- a/docs/zh-Hans/UI/Angular/Permission-Management.md
+++ b/docs/zh-Hans/UI/Angular/Permission-Management.md
@@ -53,20 +53,20 @@ export class YourComponent {
## 权限守卫
-如果你想要在导航过程中控制经过身份验证的用户对路由的访问权限,可以使用 `PermissionGuard`.
+如果你想要在导航过程中控制经过身份验证的用户对路由的访问权限,可以使用 `permissionGuard`.
-* 从@abp/ng.core导入PermissionGuard.
-* 添加 `canActivate: [PermissionGuard]` 到你的路由对象.
+* 从@abp/ng.core导入permissionGuard.
+* 添加 `canActivate: [permissionGuard]` 到你的路由对象.
* 添加 `requiredPolicy` 到路由模块路由的 `data` 属性.
```js
-import { PermissionGuard } from '@abp/ng.core';
+import { permissionGuard } from '@abp/ng.core';
// ...
const routes: Routes = [
{
path: 'path',
component: YourComponent,
- canActivate: [PermissionGuard],
+ canActivate: [permissionGuard],
data: {
requiredPolicy: 'YourProjectName.YourComponent', // policy key for your component
},
diff --git a/docs/zh-Hans/UI/AspNetCore/Bundling-Minification.md b/docs/zh-Hans/UI/AspNetCore/Bundling-Minification.md
index 338afe8967..72bee2eedb 100644
--- a/docs/zh-Hans/UI/AspNetCore/Bundling-Minification.md
+++ b/docs/zh-Hans/UI/AspNetCore/Bundling-Minification.md
@@ -353,6 +353,83 @@ services.Configure(options =>
});
````
+### 外部/CDN文件支持
+
+捆绑系统会自动识别外部/CDN文件,并将其添加到页面中,无需进行任何更改。
+
+#### 在`AbpBundlingOptions`中添加外部/CDN文件
+
+````csharp
+Configure(options =>
+{
+ options.StyleBundles
+ .Add("MyStyleBundle", configuration =>
+ {
+ configuration
+ .AddFiles("/styles/my-style1.css")
+ .AddFiles("/styles/my-style2.css")
+ .AddFiles("https://cdn.abp.io/bootstrap.css")
+ .AddFiles("/styles/my-style3.css")
+ .AddFiles("/styles/my-style4.css");
+ });
+
+ options.ScriptBundles
+ .Add("MyScriptBundle", configuration =>
+ {
+ configuration
+ .AddFiles("/scripts/my-script1.js")
+ .AddFiles("/scripts/my-script2.js")
+ .AddFiles("https://cdn.abp.io/bootstrap.js")
+ .AddFiles("/scripts/my-script3.js")
+ .AddFiles("/scripts/my-script4.js");
+ });
+});
+````
+
+**输出HTMl:**
+
+````html
+
+
+
+
+
+
+
+````
+
+#### 在TagHelpers中添加外部/CDN文件
+
+````html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+````
+
+**输出HTMl:**
+
+````html
+
+
+
+
+
+
+
+````
+
### 主题
主题使用标准包贡献者将库资源添加到页面布局. 主题还可以定义一些标准/全局包, 因此任何模块都可以为这些标准/全局包做出贡献. 有关更多信息, 请参阅[主题文档](Theming.md).
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming/Bundling/BlazorGlobalStyleContributor.cs b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming/Bundling/BlazorGlobalStyleContributor.cs
index 3223be417c..3a94e1a568 100644
--- a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming/Bundling/BlazorGlobalStyleContributor.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming/Bundling/BlazorGlobalStyleContributor.cs
@@ -17,5 +17,6 @@ public class BlazorGlobalStyleContributor : BundleContributor
context.Files.AddIfNotContains("/_content/Blazorise/blazorise.css");
context.Files.AddIfNotContains("/_content/Blazorise.Bootstrap5/blazorise.bootstrap5.css");
context.Files.AddIfNotContains("/_content/Blazorise.Snackbar/blazorise.snackbar.css");
+ context.Files.AddIfNotContains("/_content/Volo.Abp.BlazoriseUI/volo.abp.blazoriseui.css");
}
}
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/ComponentsComponentsBundleContributor.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/ComponentsComponentsBundleContributor.cs
index 61f42e8222..45b06e5484 100644
--- a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/ComponentsComponentsBundleContributor.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/ComponentsComponentsBundleContributor.cs
@@ -26,5 +26,6 @@ public class ComponentsComponentsBundleContributor : IBundleContributor
context.Add("_content/Blazorise/blazorise.css");
context.Add("_content/Blazorise.Bootstrap5/blazorise.bootstrap5.css");
context.Add("_content/Blazorise.Snackbar/blazorise.snackbar.css");
+ context.Add("_content/Volo.Abp.BlazoriseUI/volo.abp.blazoriseui.css");
}
}
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleConfigurationContext.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleConfigurationContext.cs
index b67c8f6d3d..bc4dc14d11 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleConfigurationContext.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleConfigurationContext.cs
@@ -8,7 +8,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling;
public class BundleConfigurationContext : IBundleConfigurationContext
{
- public List Files { get; }
+ public List Files { get; }
public IFileProvider FileProvider { get; }
@@ -18,7 +18,7 @@ public class BundleConfigurationContext : IBundleConfigurationContext
public BundleConfigurationContext(IServiceProvider serviceProvider, IFileProvider fileProvider)
{
- Files = new List();
+ Files = new List();
ServiceProvider = serviceProvider;
LazyServiceProvider = ServiceProvider.GetRequiredService();
FileProvider = fileProvider;
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleConfigurationExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleConfigurationExtensions.cs
index 3ee0fe6cd9..62f7950c5a 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleConfigurationExtensions.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleConfigurationExtensions.cs
@@ -1,4 +1,5 @@
using System;
+using System.Linq;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling;
@@ -10,6 +11,18 @@ public static class BundleConfigurationExtensions
return bundleConfiguration;
}
+ public static BundleConfiguration AddFiles(this BundleConfiguration bundleConfiguration, params BundleFile[] files)
+ {
+ bundleConfiguration.Contributors.AddFiles(files);
+ return bundleConfiguration;
+ }
+
+ public static BundleConfiguration AddExternalFiles(this BundleConfiguration bundleConfiguration, params string[] files)
+ {
+ bundleConfiguration.Contributors.AddExternalFiles(files.Select(x => new BundleFile(x, true)).ToArray());
+ return bundleConfiguration;
+ }
+
public static BundleConfiguration AddContributors(this BundleConfiguration bundleConfiguration, params IBundleContributor[] contributors)
{
Check.NotNull(contributors, nameof(contributors));
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorCollectionExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorCollectionExtensions.cs
index dc0ac8bfbb..addbe7e2cd 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorCollectionExtensions.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorCollectionExtensions.cs
@@ -6,4 +6,14 @@ public static class BundleContributorCollectionExtensions
{
contributors.Add(new BundleFileContributor(files));
}
+
+ public static void AddFiles(this BundleContributorCollection contributors, params BundleFile[] files)
+ {
+ contributors.Add(new BundleFileContributor(files));
+ }
+
+ public static void AddExternalFiles(this BundleContributorCollection contributors, params BundleFile[] files)
+ {
+ contributors.Add(new BundleFileContributor(files));
+ }
}
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFile.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFile.cs
new file mode 100644
index 0000000000..fbd6c3eaa1
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFile.cs
@@ -0,0 +1,31 @@
+using System;
+
+namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling;
+
+public class BundleFile
+{
+ public string FileName { get; set; }
+
+ public bool IsExternalFile { get; set; }
+
+ public BundleFile(string fileName)
+ {
+ FileName = fileName;
+ IsExternalFile = fileName.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
+ fileName.StartsWith("https://", StringComparison.OrdinalIgnoreCase);
+ }
+
+ public BundleFile(string fileName, bool isExternalFile)
+ {
+ FileName = fileName;
+ IsExternalFile = isExternalFile;
+ }
+
+ ///
+ /// This method is used to compatible with old code.
+ ///
+ public static implicit operator BundleFile(string fileName)
+ {
+ return new BundleFile(fileName);
+ }
+}
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFileContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFileContributor.cs
index 7f218e7fbf..df55266110 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFileContributor.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleFileContributor.cs
@@ -1,22 +1,30 @@
using System;
using System.Collections.Generic;
+using System.Linq;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling;
public class BundleFileContributor : BundleContributor
{
- public string[] Files { get; }
+ public List Files { get; }
+
+ public BundleFileContributor(params BundleFile[] files)
+ {
+ Files = new List();
+ Files.AddRange(files);
+ }
public BundleFileContributor(params string[] files)
{
- Files = files ?? Array.Empty();
+ Files = new List();
+ Files.AddRange(files.Select(file => new BundleFile(file)));
}
public override void ConfigureBundle(BundleConfigurationContext context)
{
foreach (var file in Files)
{
- context.Files.AddIfNotContains(x => x.Equals(file, StringComparison.OrdinalIgnoreCase), () => file);
+ context.Files.AddIfNotContains(x => x.FileName.Equals(file.FileName, StringComparison.OrdinalIgnoreCase), () => file);
}
}
}
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/IBundleConfigurationContext.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/IBundleConfigurationContext.cs
index 61d772d638..96df341514 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/IBundleConfigurationContext.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/IBundleConfigurationContext.cs
@@ -5,5 +5,5 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling;
public interface IBundleConfigurationContext : IServiceProviderAccessor
{
- List Files { get; }
+ List Files { get; }
}
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleCacheItem.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleCacheItem.cs
index 5c3890a852..f6830cbd74 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleCacheItem.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleCacheItem.cs
@@ -5,11 +5,11 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling;
public class BundleCacheItem
{
- public List Files { get; }
+ public List Files { get; }
public List WatchDisposeHandles { get; }
- public BundleCacheItem(List files)
+ public BundleCacheItem(List files)
{
Files = files;
WatchDisposeHandles = new List();
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleManager.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleManager.cs
index 8291c2c75f..20c33e86e5 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleManager.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleManager.cs
@@ -12,7 +12,6 @@ using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling.Scripts;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling.Styles;
using Volo.Abp.AspNetCore.Mvc.UI.Resources;
-using Volo.Abp.AspNetCore.VirtualFileSystem;
using Volo.Abp.DependencyInjection;
using Volo.Abp.VirtualFileSystem;
@@ -56,18 +55,20 @@ public class BundleManager : IBundleManager, ITransientDependency
Logger = NullLogger.Instance;
}
- public virtual async Task> GetStyleBundleFilesAsync(string bundleName)
+ public virtual async Task> GetStyleBundleFilesAsync(string bundleName)
{
return await GetBundleFilesAsync(Options.StyleBundles, bundleName, StyleBundler);
}
- public virtual async Task> GetScriptBundleFilesAsync(string bundleName)
+ public virtual async Task> GetScriptBundleFilesAsync(string bundleName)
{
return await GetBundleFilesAsync(Options.ScriptBundles, bundleName, ScriptBundler);
}
- protected virtual async Task> GetBundleFilesAsync(BundleConfigurationCollection bundles, string bundleName, IBundler bundler)
+ protected virtual async Task> GetBundleFilesAsync(BundleConfigurationCollection bundles, string bundleName, IBundler bundler)
{
+ var files = new List();
+
var contributors = GetContributors(bundles, bundleName);
var bundleFiles = RequestResources.TryAdd(await GetBundleFilesAsync(contributors));
var dynamicResources = RequestResources.TryAdd(await GetDynamicResourcesAsync(contributors));
@@ -77,16 +78,45 @@ public class BundleManager : IBundleManager, ITransientDependency
return bundleFiles.Union(dynamicResources).ToImmutableList();
}
+ var localBundleFiles = new List();
+ foreach (var bundleFile in bundleFiles)
+ {
+ if (!bundleFile.IsExternalFile)
+ {
+ localBundleFiles.Add(bundleFile.FileName);
+ }
+ else
+ {
+ if (localBundleFiles.Count != 0)
+ {
+ files.AddRange(AddToBundleCache(bundleName, bundler, localBundleFiles).Files);
+ localBundleFiles.Clear();
+ }
+
+ files.Add(bundleFile);
+ }
+ }
+
+ if (localBundleFiles.Count != 0)
+ {
+ files.AddRange(AddToBundleCache(bundleName, bundler, localBundleFiles).Files);
+ }
+
+ return files.Union(dynamicResources).ToImmutableList();
+ }
+
+ private BundleCacheItem AddToBundleCache(string bundleName, IBundler bundler, List bundleFiles)
+ {
var bundleRelativePath =
Options.BundleFolderName.EnsureEndsWith('/') +
bundleName + "." + bundleFiles.JoinAsString("|").ToMd5() + "." + bundler.FileExtension;
- var cacheItem = BundleCache.GetOrAdd(bundleRelativePath, () =>
+ return BundleCache.GetOrAdd(bundleRelativePath, () =>
{
var cacheValue = new BundleCacheItem(
- new List
+ new List
{
- "/" + bundleRelativePath
+ new BundleFile("/" + bundleRelativePath)
}
);
@@ -104,8 +134,6 @@ public class BundleManager : IBundleManager, ITransientDependency
return cacheValue;
});
-
- return cacheItem.Files.Union(dynamicResources).ToImmutableList();
}
private void WatchChanges(BundleCacheItem cacheValue, List files, string bundleRelativePath)
@@ -176,7 +204,7 @@ public class BundleManager : IBundleManager, ITransientDependency
}
}
- protected async Task> GetBundleFilesAsync(List contributors)
+ protected async Task> GetBundleFilesAsync(List contributors)
{
var context = CreateBundleConfigurationContext();
@@ -198,7 +226,7 @@ public class BundleManager : IBundleManager, ITransientDependency
return context.Files;
}
- protected virtual async Task> GetDynamicResourcesAsync(List contributors)
+ protected virtual async Task> GetDynamicResourcesAsync(List contributors)
{
var context = CreateBundleConfigurationContext();
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/IBundleManager.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/IBundleManager.cs
index b4ce56cb62..067554e102 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/IBundleManager.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/IBundleManager.cs
@@ -5,7 +5,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling;
public interface IBundleManager
{
- Task> GetStyleBundleFilesAsync(string bundleName);
+ Task> GetStyleBundleFilesAsync(string bundleName);
- Task> GetScriptBundleFilesAsync(string bundleName);
+ Task> GetScriptBundleFilesAsync(string bundleName);
}
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpBundleItemTagHelper.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpBundleItemTagHelper.cs
index 1240dbef11..03ed327dc0 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpBundleItemTagHelper.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpBundleItemTagHelper.cs
@@ -1,4 +1,5 @@
using System;
+using Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling.TagHelpers;
@@ -49,7 +50,7 @@ public abstract class AbpBundleItemTagHelper : Ab
if (Src != null)
{
- return new BundleTagHelperFileItem(Src);
+ return new BundleTagHelperFileItem(new BundleFile(Src));
}
throw new AbpException("abp-script tag helper requires to set either src or type!");
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperResourceService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperResourceService.cs
index a247022674..9be91c535f 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperResourceService.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperResourceService.cs
@@ -3,10 +3,12 @@ using Microsoft.AspNetCore.Razor.TagHelpers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.IO;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Rendering;
+using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
@@ -63,18 +65,24 @@ public abstract class AbpTagHelperResourceService : ITransientDependency
foreach (var bundleFile in bundleFiles)
{
- var file = HostingEnvironment.WebRootFileProvider.GetFileInfo(bundleFile);
-
- if (file == null || !file.Exists)
+ if (bundleFile.IsExternalFile)
{
- Logger.LogError($"Could not find the bundle file '{bundleFile}' for the bundle '{bundleName}'!");
- AddErrorScript(viewContext, tagHelper, context, output, bundleFile, bundleName!);
- continue;
+ AddHtmlTag(viewContext, tagHelper, context, output, bundleFile, null);
}
-
- if (file.Length > 0)
+ else
{
- AddHtmlTag(viewContext, tagHelper, context, output, bundleFile + "?_v=" + file.LastModified.UtcTicks);
+ var file = HostingEnvironment.WebRootFileProvider.GetFileInfo(bundleFile.FileName);
+ if (file == null || !file.Exists)
+ {
+ Logger.LogError($"Could not find the bundle file '{bundleFile.FileName}' for the bundle '{bundleName}'!");
+ AddErrorScript(viewContext, tagHelper, context, output, bundleFile, bundleName!);
+ continue;
+ }
+
+ if (file.Length > 0)
+ {
+ AddHtmlTag(viewContext, tagHelper, context, output, bundleFile, file);
+ }
}
}
@@ -84,13 +92,13 @@ public abstract class AbpTagHelperResourceService : ITransientDependency
protected abstract void CreateBundle(string bundleName, List bundleItems);
- protected abstract Task> GetBundleFilesAsync(string bundleName);
+ protected abstract Task> GetBundleFilesAsync(string bundleName);
- protected abstract void AddHtmlTag(ViewContext viewContext, TagHelper tagHelper, TagHelperContext context, TagHelperOutput output, string file);
+ protected abstract void AddHtmlTag(ViewContext viewContext, TagHelper tagHelper, TagHelperContext context, TagHelperOutput output, BundleFile file, IFileInfo? fileInfo = null);
- protected virtual void AddErrorScript(ViewContext viewContext, TagHelper tagHelper, TagHelperContext context, TagHelperOutput output, string file, string bundleName)
+ protected virtual void AddErrorScript(ViewContext viewContext, TagHelper tagHelper, TagHelperContext context, TagHelperOutput output, BundleFile file, string bundleName)
{
- output.Content.AppendHtml($"{Environment.NewLine}");
+ output.Content.AppendHtml($"{Environment.NewLine}");
}
protected virtual string GenerateBundleName(List bundleItems)
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperScriptService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperScriptService.cs
index 260f28f812..c8486ebadd 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperScriptService.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperScriptService.cs
@@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
+using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
@@ -32,12 +33,12 @@ public class AbpTagHelperScriptService : AbpTagHelperResourceService
);
}
- protected override async Task> GetBundleFilesAsync(string bundleName)
+ protected override async Task> GetBundleFilesAsync(string bundleName)
{
return await BundleManager.GetScriptBundleFilesAsync(bundleName);
}
- protected override void AddHtmlTag(ViewContext viewContext, TagHelper tagHelper, TagHelperContext context, TagHelperOutput output, string file)
+ protected override void AddHtmlTag(ViewContext viewContext, TagHelper tagHelper, TagHelperContext context, TagHelperOutput output, BundleFile file, IFileInfo? fileInfo = null)
{
var defer = tagHelper switch
{
@@ -46,12 +47,13 @@ public class AbpTagHelperScriptService : AbpTagHelperResourceService
_ => false
};
- var deferText = (defer || Options.DeferScriptsByDefault || Options.DeferScripts.Any(x => file.StartsWith(x, StringComparison.OrdinalIgnoreCase)))
- ? "defer"
+ var deferText = (defer || Options.DeferScriptsByDefault || Options.DeferScripts.Any(x => file.FileName.StartsWith(x, StringComparison.OrdinalIgnoreCase)))
+ ? "defer "
: string.Empty;
var nonceText = (viewContext.HttpContext.Items.TryGetValue(AbpAspNetCoreConsts.ScriptNonceKey, out var nonce) && nonce is string nonceString && !string.IsNullOrEmpty(nonceString))
- ? $"nonce=\"{nonceString}\""
+ ? $"nonce=\"{nonceString}\" "
: string.Empty;
- output.Content.AppendHtml($"{Environment.NewLine}");
+ var src = file.IsExternalFile ? file.FileName : viewContext.GetUrlHelper().Content((file.FileName + "?_v=" + fileInfo!.LastModified.UtcTicks).EnsureStartsWith('~'));
+ output.Content.AppendHtml($"{Environment.NewLine}");
}
}
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperStyleService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperStyleService.cs
index 0f68433f4c..bcfd7d95e2 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperStyleService.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperStyleService.cs
@@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
+using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Security;
@@ -18,7 +19,7 @@ public class AbpTagHelperStyleService : AbpTagHelperResourceService
public AbpTagHelperStyleService(
IBundleManager bundleManager,
IOptions options,
- IWebHostEnvironment hostingEnvironment,
+ IWebHostEnvironment hostingEnvironment,
IOptions securityHeadersOptions) : base(
bundleManager,
options,
@@ -36,12 +37,12 @@ public class AbpTagHelperStyleService : AbpTagHelperResourceService
);
}
- protected override async Task> GetBundleFilesAsync(string bundleName)
+ protected override async Task> GetBundleFilesAsync(string bundleName)
{
return await BundleManager.GetStyleBundleFilesAsync(bundleName);
}
- protected override void AddHtmlTag(ViewContext viewContext, TagHelper tagHelper, TagHelperContext context, TagHelperOutput output, string file)
+ protected override void AddHtmlTag(ViewContext viewContext, TagHelper tagHelper, TagHelperContext context, TagHelperOutput output, BundleFile file, IFileInfo? fileInfo = null)
{
var preload = tagHelper switch
{
@@ -50,15 +51,16 @@ public class AbpTagHelperStyleService : AbpTagHelperResourceService
_ => false
};
- if (preload || Options.PreloadStylesByDefault || Options.PreloadStyles.Any(x => file.StartsWith(x, StringComparison.OrdinalIgnoreCase)))
+ var href = file.IsExternalFile ? file.FileName : viewContext.GetUrlHelper().Content((file.FileName + "?_v=" + fileInfo!.LastModified.UtcTicks).EnsureStartsWith('~'));
+ if (preload || Options.PreloadStylesByDefault || Options.PreloadStyles.Any(x => file.FileName.StartsWith(x, StringComparison.OrdinalIgnoreCase)))
{
output.Content.AppendHtml(SecurityHeadersOptions.UseContentSecurityPolicyScriptNonce
- ? $"{Environment.NewLine}"
- : $"{Environment.NewLine}");
+ ? $"{Environment.NewLine}"
+ : $"{Environment.NewLine}");
}
else
{
- output.Content.AppendHtml($"{Environment.NewLine}");
+ output.Content.AppendHtml($"{Environment.NewLine}");
}
}
}
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/BundleTagHelperFileItem.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/BundleTagHelperFileItem.cs
index 45ab8b64eb..b5f6ca74aa 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/BundleTagHelperFileItem.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/BundleTagHelperFileItem.cs
@@ -5,16 +5,16 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling.TagHelpers;
public class BundleTagHelperFileItem : BundleTagHelperItem
{
[NotNull]
- public string File { get; }
+ public BundleFile File { get; }
- public BundleTagHelperFileItem([NotNull] string file)
+ public BundleTagHelperFileItem([NotNull] BundleFile file)
{
File = Check.NotNull(file, nameof(file));
}
public override string ToString()
{
- return File;
+ return File.FileName;
}
public override void AddToConfiguration(BundleConfiguration configuration)
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Resources/IWebRequestResources.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Resources/IWebRequestResources.cs
index 2eefbcc0dd..9479dec8ce 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Resources/IWebRequestResources.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Resources/IWebRequestResources.cs
@@ -1,8 +1,9 @@
using System.Collections.Generic;
+using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
namespace Volo.Abp.AspNetCore.Mvc.UI.Resources;
public interface IWebRequestResources
{
- List TryAdd(List resources);
+ List TryAdd(List resources);
}
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Resources/WebRequestResources.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Resources/WebRequestResources.cs
index 3e046499ab..e68b671fc6 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Resources/WebRequestResources.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Resources/WebRequestResources.cs
@@ -1,23 +1,24 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Http;
+using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.Mvc.UI.Resources;
public class WebRequestResources : IWebRequestResources, IScopedDependency
{
- protected Dictionary> Resources { get; }
+ protected Dictionary> Resources { get; }
protected IHttpContextAccessor HttpContextAccessor { get; }
public WebRequestResources(IHttpContextAccessor httpContextAccessor)
{
HttpContextAccessor = httpContextAccessor;
- Resources = new Dictionary>();
+ Resources = new Dictionary>();
}
- public List TryAdd(List resources)
+ public List TryAdd(List resources)
{
var path = HttpContextAccessor.HttpContext?.Request?.Path ?? "";
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/AbpAspNetCoreMvcUiPackagesModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/AbpAspNetCoreMvcUiPackagesModule.cs
index 4970015a46..7ba8105d43 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/AbpAspNetCoreMvcUiPackagesModule.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/AbpAspNetCoreMvcUiPackagesModule.cs
@@ -30,11 +30,13 @@ public class AbpAspNetCoreMvcUiPackagesModule : AbpModule
//moment
options.AddLanguagesMapOrUpdate(MomentScriptContributor.PackageName,
new NameValue("zh-Hans", "zh-cn"),
- new NameValue("zh-Hant", "zh-tw"));
+ new NameValue("zh-Hant", "zh-tw"),
+ new NameValue("de-DE", "de"));
options.AddLanguageFilesMapOrUpdate(MomentScriptContributor.PackageName,
new NameValue("zh-Hans", "zh-cn"),
- new NameValue("zh-Hant", "zh-tw"));
+ new NameValue("zh-Hant", "zh-tw"),
+ new NameValue("de-DE", "de"));
//Timeago
options.AddLanguageFilesMapOrUpdate(TimeagoScriptContributor.PackageName,
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs
index 2ac299959a..2ca7c191c8 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs
@@ -35,20 +35,20 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Bundling;
)]
public class SharedThemeGlobalScriptContributor : BundleContributor
{
-
+
public override void ConfigureBundle(BundleConfigurationContext context)
{
- context.Files.AddRange(new[]
+ context.Files.AddRange(new BundleFile[]
{
- "/libs/abp/aspnetcore-mvc-ui-theme-shared/ui-extensions.js",
- "/libs/abp/aspnetcore-mvc-ui-theme-shared/jquery/jquery-extensions.js",
- "/libs/abp/aspnetcore-mvc-ui-theme-shared/jquery-form/jquery-form-extensions.js",
- "/libs/abp/aspnetcore-mvc-ui-theme-shared/jquery/widget-manager.js",
- "/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js",
- "/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/modal-manager.js",
- "/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js",
- "/libs/abp/aspnetcore-mvc-ui-theme-shared/sweetalert2/abp-sweetalert2.js",
- "/libs/abp/aspnetcore-mvc-ui-theme-shared/toastr/abp-toastr.js"
+ "/libs/abp/aspnetcore-mvc-ui-theme-shared/ui-extensions.js",
+ "/libs/abp/aspnetcore-mvc-ui-theme-shared/jquery/jquery-extensions.js",
+ "/libs/abp/aspnetcore-mvc-ui-theme-shared/jquery-form/jquery-form-extensions.js",
+ "/libs/abp/aspnetcore-mvc-ui-theme-shared/jquery/widget-manager.js",
+ "/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js",
+ "/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/modal-manager.js",
+ "/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js",
+ "/libs/abp/aspnetcore-mvc-ui-theme-shared/sweetalert2/abp-sweetalert2.js",
+ "/libs/abp/aspnetcore-mvc-ui-theme-shared/toastr/abp-toastr.js"
});
}
}
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalStyleContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalStyleContributor.cs
index 01f490425c..f959d73f05 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalStyleContributor.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalStyleContributor.cs
@@ -27,10 +27,10 @@ public class SharedThemeGlobalStyleContributor : BundleContributor
{
public override void ConfigureBundle(BundleConfigurationContext context)
{
- context.Files.AddRange(new[]
+ context.Files.AddRange(new BundleFile[]
{
- "/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-styles.css",
- "/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-styles.css"
- });
+ "/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-styles.css",
+ "/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-styles.css"
+ });
}
}
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js
index 1baa46de9a..8e567d626a 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js
@@ -417,7 +417,7 @@
var isUtc = options.isUtc;
dateFormat = dateFormat || options.dateFormat;
if (!date) {
- return isUtc ? moment.utc() : moment();
+ return isUtc ? moment.utc().startOf('day') : moment().startOf('day');
}
if (isUtc) {
diff --git a/framework/src/Volo.Abp.BlazoriseUI/wwwroot/volo.abp.blazoriseui.css b/framework/src/Volo.Abp.BlazoriseUI/wwwroot/volo.abp.blazoriseui.css
new file mode 100644
index 0000000000..84814c2ef3
--- /dev/null
+++ b/framework/src/Volo.Abp.BlazoriseUI/wwwroot/volo.abp.blazoriseui.css
@@ -0,0 +1,60 @@
+.radar-spinner, .radar-spinner * {
+ box-sizing: border-box;
+}
+
+.radar-spinner {
+ height: 60px;
+ width: 60px;
+ position: relative;
+}
+
+.radar-spinner .circle {
+ position: absolute;
+ height: 100%;
+ width: 100%;
+ top: 0;
+ left: 0;
+ animation: radar-spinner-animation 2s infinite;
+}
+
+.radar-spinner .circle:nth-child(1) {
+ padding: calc(60px * 5 * 2 * 0 / 110);
+ animation-delay: 300ms;
+}
+
+.radar-spinner .circle:nth-child(2) {
+ padding: calc(60px * 5 * 2 * 1 / 110);
+ animation-delay: 300ms;
+}
+
+.radar-spinner .circle:nth-child(3) {
+ padding: calc(60px * 5 * 2 * 2 / 110);
+ animation-delay: 300ms;
+}
+
+.radar-spinner .circle:nth-child(4) {
+ padding: calc(60px * 5 * 2 * 3 / 110);
+ animation-delay: 0ms;
+}
+
+.radar-spinner .circle-inner, .radar-spinner .circle-inner-container {
+ height: 100%;
+ width: 100%;
+ border-radius: 50%;
+ border: calc(60px * 5 / 110) solid transparent;
+}
+
+.radar-spinner .circle-inner {
+ border-left-color: var(--secondary, #ff1d5e);
+ border-right-color: var(--secondary, #ff1d5e);
+}
+
+@keyframes radar-spinner-animation {
+ 50% {
+ transform: rotate(180deg);
+ }
+
+ 100% {
+ transform: rotate(0deg);
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Dapr/Volo.Abp.Dapr.csproj b/framework/src/Volo.Abp.Dapr/Volo.Abp.Dapr.csproj
index 92377ef51a..34a505110c 100644
--- a/framework/src/Volo.Abp.Dapr/Volo.Abp.Dapr.csproj
+++ b/framework/src/Volo.Abp.Dapr/Volo.Abp.Dapr.csproj
@@ -12,6 +12,7 @@
+
diff --git a/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/AbpDaprClientFactory.cs b/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/AbpDaprClientFactory.cs
index 43e9dbc953..92a2961cd4 100644
--- a/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/AbpDaprClientFactory.cs
+++ b/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/AbpDaprClientFactory.cs
@@ -1,10 +1,14 @@
using System;
+using System.Globalization;
using System.Net.Http;
+using System.Net.Http.Headers;
using System.Text.Json;
using Dapr.Client;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Json.SystemTextJson;
+using Volo.Abp.MultiTenancy;
+using Volo.Abp.Tracing;
namespace Volo.Abp.Dapr;
@@ -13,13 +17,21 @@ public class AbpDaprClientFactory : IAbpDaprClientFactory, ISingletonDependency
protected AbpDaprOptions DaprOptions { get; }
protected JsonSerializerOptions JsonSerializerOptions { get; }
protected IDaprApiTokenProvider DaprApiTokenProvider { get; }
+ protected ICurrentTenant CurrentTenant { get; }
+ protected ICorrelationIdProvider CorrelationIdProvider { get; }
+ protected IOptions AbpCorrelationIdOptions { get; }
public AbpDaprClientFactory(
IOptions options,
IOptions systemTextJsonSerializerOptions,
- IDaprApiTokenProvider daprApiTokenProvider)
+ IDaprApiTokenProvider daprApiTokenProvider,
+ ICurrentTenant currentTenant, ICorrelationIdProvider correlationIdProvider,
+ IOptions abpCorrelationIdOptions)
{
DaprApiTokenProvider = daprApiTokenProvider;
+ CurrentTenant = currentTenant;
+ CorrelationIdProvider = correlationIdProvider;
+ AbpCorrelationIdOptions = abpCorrelationIdOptions;
DaprOptions = options.Value;
JsonSerializerOptions = CreateJsonSerializerOptions(systemTextJsonSerializerOptions.Value);
}
@@ -61,11 +73,39 @@ public class AbpDaprClientFactory : IAbpDaprClientFactory, ISingletonDependency
daprEndpoint = DaprOptions.HttpEndpoint;
}
- return DaprClient.CreateInvokeHttpClient(
+ var httpClient = DaprClient.CreateInvokeHttpClient(
appId,
daprEndpoint,
daprApiToken ?? DaprApiTokenProvider.GetDaprApiToken()
);
+
+ AddHeaders(httpClient);
+
+ return httpClient;
+ }
+
+ protected virtual void AddHeaders(HttpClient httpClient)
+ {
+ //CorrelationId
+ httpClient.DefaultRequestHeaders.Add(AbpCorrelationIdOptions.Value.HttpHeaderName, CorrelationIdProvider.Get());
+
+ //TenantId
+ if (CurrentTenant.Id.HasValue)
+ {
+ //TODO: Use AbpAspNetCoreMultiTenancyOptions to get the key
+ httpClient.DefaultRequestHeaders.Add(TenantResolverConsts.DefaultTenantKey, CurrentTenant.Id.Value.ToString());
+ }
+
+ //Culture
+ //TODO: Is that the way we want? Couldn't send the culture (not ui culture)
+ var currentCulture = CultureInfo.CurrentUICulture.Name ?? CultureInfo.CurrentCulture.Name;
+ if (!currentCulture.IsNullOrEmpty())
+ {
+ httpClient.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue(currentCulture));
+ }
+
+ //X-Requested-With
+ httpClient.DefaultRequestHeaders.Add("X-Requested-With", "XMLHttpRequest");
}
protected virtual JsonSerializerOptions CreateJsonSerializerOptions(AbpSystemTextJsonSerializerOptions systemTextJsonSerializerOptions)
diff --git a/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/AbpDaprModule.cs b/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/AbpDaprModule.cs
index e1ca4bb0db..65605bc342 100644
--- a/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/AbpDaprModule.cs
+++ b/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/AbpDaprModule.cs
@@ -4,10 +4,11 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Volo.Abp.Json;
using Volo.Abp.Modularity;
+using Volo.Abp.MultiTenancy;
namespace Volo.Abp.Dapr;
-[DependsOn(typeof(AbpJsonModule))]
+[DependsOn(typeof(AbpJsonModule), typeof(AbpMultiTenancyAbstractionsModule))]
public class AbpDaprModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs
index 3274360ba2..799397218c 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs
+++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs
@@ -161,16 +161,18 @@ public class EfCoreRepository : RepositoryBase, IE
{
var dbContext = await GetDbContextAsync();
- dbContext.Attach(entity);
-
- var updatedEntity = dbContext.Update(entity).Entity;
+ if (dbContext.Set().Local.All(e => e != entity))
+ {
+ dbContext.Set().Attach(entity);
+ dbContext.Update(entity);
+ }
if (autoSave)
{
await dbContext.SaveChangesAsync(GetCancellationToken(cancellationToken));
}
- return updatedEntity;
+ return entity;
}
public async override Task UpdateManyAsync(IEnumerable entities, bool autoSave = false, CancellationToken cancellationToken = default)
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs
index cb97111c59..dc3754be3b 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs
+++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs
@@ -85,8 +85,10 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency
case EntityState.Modified:
changeType = IsDeleted(entityEntry) ? EntityChangeType.Deleted : EntityChangeType.Updated;
break;
- case EntityState.Detached:
case EntityState.Unchanged:
+ changeType = EntityChangeType.Updated; // Navigation property changes.
+ break;
+ case EntityState.Detached:
default:
return null;
}
@@ -184,6 +186,21 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency
}
}
+ if (entityEntry.State == EntityState.Unchanged)
+ {
+ foreach (var navigation in entityEntry.Navigations)
+ {
+ if (navigation.IsModified || (navigation is ReferenceEntry && navigation.As().TargetEntry?.State == EntityState.Modified))
+ {
+ propertyChanges.Add(new EntityPropertyChangeInfo
+ {
+ PropertyName = navigation.Metadata.Name,
+ PropertyTypeFullName = navigation.Metadata.ClrType.GetFirstGenericArgumentIfNullable().FullName!
+ });
+ }
+ }
+ }
+
return propertyChanges;
}
@@ -205,12 +222,26 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency
protected virtual bool ShouldSaveEntityHistory(EntityEntry entityEntry, bool defaultValue = false)
{
- if (entityEntry.State == EntityState.Detached ||
- entityEntry.State == EntityState.Unchanged)
+ if (entityEntry.State == EntityState.Detached)
{
return false;
}
+ if (entityEntry.State == EntityState.Unchanged)
+ {
+ if (entityEntry.Navigations.Any(navigationEntry => navigationEntry.IsModified))
+ {
+ return true;
+ }
+
+ if (entityEntry.Navigations.Where(x => x is ReferenceEntry).Cast().Any(x => x.TargetEntry != null && x.TargetEntry.State == EntityState.Modified))
+ {
+ return true;
+ }
+
+ return false;
+ }
+
var entityType = entityEntry.Metadata.ClrType;
if (!EntityHelper.IsEntity(entityType) && !EntityHelper.IsValueObject(entityType))
diff --git a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/StandardMenus.cs b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/StandardMenus.cs
index c22645e33a..83f7f70cc9 100644
--- a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/StandardMenus.cs
+++ b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/StandardMenus.cs
@@ -2,12 +2,6 @@ namespace Volo.Abp.UI.Navigation;
public static class StandardMenus
{
- /* TODO: Consider to create nested class like
- * StandardMenus.Application.Main
- * StandardMenus.Application.User
- * StandardMenus.Application.Shortcut
- */
-
public const string Main = "Main";
public const string User = "User";
public const string Shortcut = "Shortcut";
diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json
index bcbab76211..a7de87d5ef 100644
--- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json
+++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json
@@ -49,6 +49,8 @@
"ItemWillBeDeletedMessageWithFormat": "سيتم حذف {0}!",
"ItemWillBeDeletedMessage": "سوف يتم حذف هذا البند!",
"ManageYourAccount": "إدارة حسابك",
- "OthersGroup": "آخرون"
+ "OthersGroup": "آخرون",
+ "Today": "اليوم",
+ "Apply": "يتقدم"
}
}
diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json
index 5dc41952a6..a1f1856c9e 100644
--- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json
+++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json
@@ -49,6 +49,8 @@
"ItemWillBeDeletedMessageWithFormat": "{0} bude smazáno!",
"ItemWillBeDeletedMessage": "Tato položka bude smazána!",
"ManageYourAccount": "Spravujte svůj účet",
- "OthersGroup": "Jiný"
+ "OthersGroup": "Jiný",
+ "Today": "Dnes",
+ "Apply": "Aplikovat"
}
}
diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/de.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/de.json
index dc99f75586..22fddc3e27 100644
--- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/de.json
+++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/de.json
@@ -49,6 +49,8 @@
"ItemWillBeDeletedMessageWithFormat": "{0} wird gelöscht!",
"ItemWillBeDeletedMessage": "Dieses Element wird gelöscht!",
"ManageYourAccount": "Verwalten Sie Ihr Benutzerkonto",
- "OthersGroup":"Andere"
+ "OthersGroup":"Andere",
+ "Today": "Heute",
+ "Apply": "Anwenden"
}
}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/el.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/el.json
index 24a7642eb0..8cb7b4c0dc 100644
--- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/el.json
+++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/el.json
@@ -49,6 +49,8 @@
"ItemWillBeDeletedMessageWithFormat": "Το {0} θα διαγραφεί",
"ItemWillBeDeletedMessage": "Αυτό το στοιχείο θα διαγραφεί!",
"ManageYourAccount": "Διαχείριση Λογαριασμού",
- "OthersGroup":"άλλος"
+ "OthersGroup":"άλλος",
+ "Today": "Σήμερα",
+ "Apply": "Ισχύουν"
}
}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en-GB.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en-GB.json
index c6ea44f1f3..68ad3dda07 100644
--- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en-GB.json
+++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en-GB.json
@@ -50,6 +50,8 @@
"ItemWillBeDeletedMessage": "This item will be deleted!",
"ManageYourAccount": "Manage your account",
"OthersGroup": "Other",
- "NotAssigned": "Not Assigned"
+ "NotAssigned": "Not Assigned",
+ "Today": "Today",
+ "Apply": "Apply"
}
}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/es.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/es.json
index c28e8049bc..abf136d87b 100644
--- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/es.json
+++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/es.json
@@ -49,6 +49,8 @@
"ItemWillBeDeletedMessageWithFormat": "{0} serán borrados!",
"ItemWillBeDeletedMessage": "Este elemento será borrado",
"ManageYourAccount": "Administrar cuenta",
- "OthersGroup": "Otra"
+ "OthersGroup": "Otra",
+ "Today": "Hoy",
+ "Apply": "Aplicar"
}
}
diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fa.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fa.json
index 4fd179573b..bcc6250656 100644
--- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fa.json
+++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fa.json
@@ -49,6 +49,8 @@
"ItemWillBeDeletedMessageWithFormat": "{0} حذف خواهد شد!",
"ItemWillBeDeletedMessage": "این مورد حذف خواهد شد!",
"ManageYourAccount": "حساب خود را مدیریت کنید",
- "OthersGroup": "دیگر"
+ "OthersGroup": "دیگر",
+ "Today": "امروز",
+ "Apply": "درخواست دادن"
}
}
diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fi.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fi.json
index 25811c92eb..25ab3ee59a 100644
--- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fi.json
+++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fi.json
@@ -49,6 +49,8 @@
"ItemWillBeDeletedMessageWithFormat": "{0} poistetaan!",
"ItemWillBeDeletedMessage": "Tämä kohde poistetaan!",
"ManageYourAccount": "Hallitse tiliäsi",
- "OthersGroup": "Muut"
+ "OthersGroup": "Muut",
+ "Today": "Tänään",
+ "Apply": "Käytä"
}
}
diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fr.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fr.json
index b850315dfe..6e5c67d2d6 100644
--- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fr.json
+++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fr.json
@@ -49,6 +49,8 @@
"ItemWillBeDeletedMessageWithFormat": "{0} sera supprimé!",
"ItemWillBeDeletedMessage": "Cet objet va être supprimé!",
"ManageYourAccount": "Gérer votre compte",
- "OthersGroup": "Autre"
+ "OthersGroup": "Autre",
+ "Today": "Aujourd'hui",
+ "Apply": "Appliquer"
}
}
diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hi.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hi.json
index 0a20f1dcbb..96b73848f5 100644
--- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hi.json
+++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hi.json
@@ -49,6 +49,8 @@
"ItemWillBeDeletedMessageWithFormat": "{0} हटा दिया जाएगा!",
"ItemWillBeDeletedMessage": "यह आइटम हटा दिया जाएगा!",
"ManageYourAccount": "अपने खाते का प्रबंधन",
- "OthersGroup": "अन्य"
+ "OthersGroup": "अन्य",
+ "Today": "आज",
+ "Apply": "आवेदन करना"
}
}
diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hr.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hr.json
index a6285a4cee..3126f17bbf 100644
--- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hr.json
+++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hr.json
@@ -49,6 +49,8 @@
"ItemWillBeDeletedMessageWithFormat": "{0} zapis će biti obrisan!",
"ItemWillBeDeletedMessage": "Ovaj zapis će biti obrisan!",
"ManageYourAccount": "Upravljaj korisničkim računom",
- "OthersGroup": "Drugi"
+ "OthersGroup": "Drugi",
+ "Today": "Danas",
+ "Apply": "primijeniti"
}
}
diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hu.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hu.json
index 98c0c46f54..f3e6454b5b 100644
--- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hu.json
+++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hu.json
@@ -49,6 +49,8 @@
"ItemWillBeDeletedMessageWithFormat": "{0} törlésre kerül!",
"ItemWillBeDeletedMessage": "Ez az elem törlődik!",
"ManageYourAccount": "Kezelje fiókját",
- "OthersGroup": "Egyéb"
+ "OthersGroup": "Egyéb",
+ "Today": "Ma",
+ "Apply": "Alkalmaz"
}
}
diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/is.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/is.json
index 12b1565c2e..5a9d9eab54 100644
--- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/is.json
+++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/is.json
@@ -49,6 +49,8 @@
"ItemWillBeDeletedMessageWithFormat": "{0} verður eytt!",
"ItemWillBeDeletedMessage": "Þessum lið verður eytt!",
"ManageYourAccount": "Stillingar notandaaðgangs",
- "OthersGroup": "Annað"
+ "OthersGroup": "Annað",
+ "Today": "Í dag",
+ "Apply": "Sækja um"
}
}
diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/it.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/it.json
index f07bf67e2d..171681980d 100644
--- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/it.json
+++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/it.json
@@ -49,6 +49,8 @@
"ItemWillBeDeletedMessageWithFormat": "{0} sarà eliminato!",
"ItemWillBeDeletedMessage": "Questo elemento sarà eliminato!",
"ManageYourAccount": "Gestisci il tuo account",
- "OthersGroup": "Altra"
+ "OthersGroup": "Altra",
+ "Today": "Oggi",
+ "Apply": "Fare domanda a"
}
}
diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/nl.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/nl.json
index 5875fe07b9..42d48d5900 100644
--- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/nl.json
+++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/nl.json
@@ -49,6 +49,8 @@
"ItemWillBeDeletedMessageWithFormat": "{0} wordt verwijderd!",
"ItemWillBeDeletedMessage": "Dit item wordt verwijderd!",
"ManageYourAccount": "Beheer uw account",
- "OthersGroup": "Ander"
+ "OthersGroup": "Ander",
+ "Today": "Vandaag",
+ "Apply": "Toepassen"
}
}
diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl-PL.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl-PL.json
index f2b8227137..bd7a6d5818 100644
--- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl-PL.json
+++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl-PL.json
@@ -49,6 +49,8 @@
"ItemWillBeDeletedMessageWithFormat": "{0} zostanie usunięty!",
"ItemWillBeDeletedMessage": "Ten element zostanie usunięty!",
"ManageYourAccount": "Zarządzaj kontem",
- "OthersGroup": "Inny"
+ "OthersGroup": "Inny",
+ "Today": "Dzisiaj",
+ "Apply": "Stosować"
}
}
diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pt-BR.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pt-BR.json
index 928c9299fb..a205b9442a 100644
--- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pt-BR.json
+++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pt-BR.json
@@ -49,6 +49,8 @@
"ItemWillBeDeletedMessageWithFormat": "{0} será excluído!",
"ItemWillBeDeletedMessage": "Este item será excluído!",
"ManageYourAccount": "Gerenciar sua conta",
- "OthersGroup": "Outra"
+ "OthersGroup": "Outra",
+ "Today": "Hoje",
+ "Apply": "Aplicar"
}
}
diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ro-RO.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ro-RO.json
index 544b893c39..0edf60e833 100644
--- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ro-RO.json
+++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ro-RO.json
@@ -49,6 +49,8 @@
"ItemWillBeDeletedMessageWithFormat": "{0} va fi şters!",
"ItemWillBeDeletedMessage": "Acest articol va fi şters!",
"ManageYourAccount": "Administraţi-vă contul",
- "OthersGroup": "Alte"
+ "OthersGroup": "Alte",
+ "Today": "Astăzi",
+ "Apply": "aplica"
}
}
diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ru.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ru.json
index 66f24ca65d..53104c18f4 100644
--- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ru.json
+++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ru.json
@@ -49,6 +49,8 @@
"ItemWillBeDeletedMessageWithFormat": "{0} будет удален!",
"ItemWillBeDeletedMessage": "Этот предмет будет удален!",
"ManageYourAccount": "Настройте свой аккаунт",
- "OthersGroup": "Другой"
+ "OthersGroup": "Другой",
+ "Today": "Сегодня",
+ "Apply": "Применять"
}
}
diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sk.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sk.json
index 28278f04f2..313ce021de 100644
--- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sk.json
+++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sk.json
@@ -49,6 +49,8 @@
"ItemWillBeDeletedMessageWithFormat": "{0} sa vymaže!",
"ItemWillBeDeletedMessage": "Táto položka bude vymazaná!",
"ManageYourAccount": "Spravovať svoje konto",
- "OthersGroup": "Iné"
+ "OthersGroup": "Iné",
+ "Today": "Dnes",
+ "Apply": "Použiť"
}
}
diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sl.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sl.json
index c05c1dc202..e7ff5ddc66 100644
--- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sl.json
+++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sl.json
@@ -49,6 +49,8 @@
"ItemWillBeDeletedMessageWithFormat": "{0} bo izbrisan!",
"ItemWillBeDeletedMessage": "Ta element bo izbrisan!",
"ManageYourAccount": "Upravljajte svoj račun",
- "OthersGroup": "Ostalo"
+ "OthersGroup": "Ostalo",
+ "Today": "Danes",
+ "Apply": "Prijavite se"
}
}
diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/vi.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/vi.json
index 1b8bc5fb5b..581964a9b8 100644
--- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/vi.json
+++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/vi.json
@@ -49,6 +49,8 @@
"ItemWillBeDeletedMessageWithFormat": "{0} sẽ bị xóa!",
"ItemWillBeDeletedMessage": "Vật phẩm này sẽ bị xoá!",
"ManageYourAccount": "Quản lý tài khoản của bạn",
- "OthersGroup": "Khác"
+ "OthersGroup": "Khác",
+ "Today": "Hôm nay",
+ "Apply": "Áp dụng"
}
}
diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json
index bcf68dbcaa..51d5865924 100644
--- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json
+++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json
@@ -51,6 +51,8 @@
"ManageYourAccount": "管理你的账户",
"OthersGroup": "其他",
"CopiedToTheClipboard": "已复制到剪贴板",
- "NotAssigned": "未分配"
+ "NotAssigned": "未分配",
+ "Today": "今天",
+ "Apply": "应用"
}
}
diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json
index 1c2719b954..dac062d1cd 100644
--- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json
+++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json
@@ -51,6 +51,8 @@
"ManageYourAccount": "管理個人帳號",
"OthersGroup": "其他",
"CopiedToTheClipboard": "已复制到剪贴板",
- "NotAssigned": "未分配"
+ "NotAssigned": "未分配",
+ "Today": "今天",
+ "Apply": "應用"
}
}
diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithNavigations.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithNavigations.cs
new file mode 100644
index 0000000000..e68d9d94b8
--- /dev/null
+++ b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithNavigations.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+using Volo.Abp.Domain.Entities;
+
+namespace Volo.Abp.Auditing.App.Entities;
+
+[Audited]
+public class AppEntityWithNavigations : AggregateRoot
+{
+ protected AppEntityWithNavigations()
+ {
+
+ }
+
+ public AppEntityWithNavigations(Guid id, string name)
+ : base(id)
+ {
+ Name = name;
+ FullName = name;
+ }
+
+ public string Name { get; set; }
+
+ public string FullName { get; set; }
+
+ public virtual AppEntityWithNavigationChildOneToOne OneToOne { get; set; }
+
+ public virtual List OneToMany { get; set; }
+
+ public virtual List ManyToMany { get; set; }
+}
+
+[Audited]
+public class AppEntityWithNavigationChildOneToOne : Entity
+{
+ public string ChildName { get; set; }
+}
+
+[Audited]
+public class AppEntityWithNavigationChildOneToMany : Entity
+{
+ public Guid AppEntityWithNavigationId { get; set; }
+
+ public string ChildName { get; set; }
+}
+
+[Audited]
+public class AppEntityWithNavigationChildManyToMany : Entity
+{
+ public string ChildName { get; set; }
+}
diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/EntityFrameworkCore/AbpAuditingTestDbContext.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/EntityFrameworkCore/AbpAuditingTestDbContext.cs
index 8a0a798598..f6d4bf49ef 100644
--- a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/EntityFrameworkCore/AbpAuditingTestDbContext.cs
+++ b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/EntityFrameworkCore/AbpAuditingTestDbContext.cs
@@ -27,6 +27,8 @@ public class AbpAuditingTestDbContext : AbpDbContext
public DbSet AppEntityWithValueObject { get; set; }
+ public DbSet AppEntityWithNavigations { get; set; }
+
public AbpAuditingTestDbContext(DbContextOptions options)
: base(options)
{
@@ -42,5 +44,14 @@ public class AbpAuditingTestDbContext : AbpDbContext
b.ConfigureByConvention();
b.OwnsOne(v => v.AppEntityWithValueObjectAddress);
});
+
+ modelBuilder.Entity(b =>
+ {
+ b.ConfigureByConvention();
+ b.HasOne(x => x.OneToOne).WithOne().HasForeignKey(x => x.Id);
+ b.HasMany(x => x.OneToMany).WithOne().HasForeignKey(x => x.AppEntityWithNavigationId);
+ b.HasMany(x => x.ManyToMany).WithMany();
+ });
+
}
}
diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs
index 3aa0da5a9b..7154fd805c 100644
--- a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs
+++ b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs
@@ -411,14 +411,172 @@ public class Auditing_Tests : AbpAuditingTestBase
#pragma warning disable 4014
AuditingStore.Received().SaveAsync(Arg.Is(x => x.EntityChanges.Count == 2 &&
x.EntityChanges[0].ChangeType == EntityChangeType.Updated &&
- x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithValueObject).FullName &&
+ x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithValueObjectAddress).FullName &&
+ x.EntityChanges[0].PropertyChanges.Count == 1 &&
+ x.EntityChanges[0].PropertyChanges[0].PropertyName == nameof(AppEntityWithValueObjectAddress.Country) &&
+ x.EntityChanges[0].PropertyChanges[0].OriginalValue == "\"England\"" &&
+ x.EntityChanges[0].PropertyChanges[0].NewValue == "\"Germany\"" &&
+
+ x.EntityChanges[1].ChangeType == EntityChangeType.Updated &&
+ x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithValueObject).FullName &&
+ x.EntityChanges[1].PropertyChanges.Count == 1 &&
+ x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithValueObject.AppEntityWithValueObjectAddress)));
+
+#pragma warning restore 4014
+
+ using (var scope = _auditingManager.BeginScope())
+ {
+ using (var uow = _unitOfWorkManager.Begin())
+ {
+ var entity = await repository.GetAsync(entityId);
+
+ entity.AppEntityWithValueObjectAddress = null;
+
+ await repository.UpdateAsync(entity);
+ await uow.CompleteAsync();
+ await scope.SaveAsync();
+ }
+ }
+
+#pragma warning disable 4014
+ AuditingStore.Received().SaveAsync(Arg.Is(x => x.EntityChanges.Count == 2 &&
+ x.EntityChanges[0].ChangeType == EntityChangeType.Updated &&
+ x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithValueObjectAddress).FullName &&
+ x.EntityChanges[0].PropertyChanges.Count == 1 &&
+ x.EntityChanges[0].PropertyChanges[0].PropertyName == nameof(AppEntityWithValueObjectAddress.Country) &&
+ x.EntityChanges[0].PropertyChanges[0].OriginalValue == "\"England\"" &&
+ x.EntityChanges[0].PropertyChanges[0].NewValue == "\"Germany\"" &&
+
+ x.EntityChanges[1].ChangeType == EntityChangeType.Updated &&
+ x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithValueObject).FullName));
+
+#pragma warning restore 4014
+ }
+
+ [Fact]
+ public virtual async Task Should_Write_AuditLog_For_Navigations_Changes()
+ {
+ var entityId = Guid.NewGuid();
+ var repository = ServiceProvider.GetRequiredService>();
+ await repository.InsertAsync(new AppEntityWithNavigations(entityId, "test name"));
+
+ using (var scope = _auditingManager.BeginScope())
+ {
+ using (var uow = _unitOfWorkManager.Begin())
+ {
+ var entity = await repository.GetAsync(entityId);
+
+ entity.FullName = "test full name";
+
+ await repository.UpdateAsync(entity);
+
+ await uow.CompleteAsync();
+ await scope.SaveAsync();
+ }
+ }
+
+#pragma warning disable 4014
+ AuditingStore.Received().SaveAsync(Arg.Is(x => x.EntityChanges.Count == 1 &&
+ x.EntityChanges[0].ChangeType == EntityChangeType.Updated &&
+ x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithNavigations).FullName &&
+ x.EntityChanges[0].PropertyChanges.Count == 1 &&
+ x.EntityChanges[0].PropertyChanges[0].OriginalValue == "\"test name\"" &&
+ x.EntityChanges[0].PropertyChanges[0].NewValue == "\"test full name\"" &&
+ x.EntityChanges[0].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.FullName) &&
+ x.EntityChanges[0].PropertyChanges[0].PropertyTypeFullName == typeof(string).FullName));
+#pragma warning restore 4014
+ using (var scope = _auditingManager.BeginScope())
+ {
+ using (var uow = _unitOfWorkManager.Begin())
+ {
+ var entity = await repository.GetAsync(entityId);
+
+ entity.OneToOne = new AppEntityWithNavigationChildOneToOne
+ {
+ ChildName = "ChildName"
+ };
+
+ await repository.UpdateAsync(entity);
+
+ await uow.CompleteAsync();
+ await scope.SaveAsync();
+ }
+ }
+
+#pragma warning disable 4014
+ AuditingStore.Received().SaveAsync(Arg.Is(x => x.EntityChanges.Count == 2 &&
+ x.EntityChanges[0].ChangeType == EntityChangeType.Created &&
+ x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithNavigationChildOneToOne).FullName &&
+ x.EntityChanges[1].ChangeType == EntityChangeType.Updated &&
+ x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigations).FullName &&
+ x.EntityChanges[1].PropertyChanges.Count == 1 &&
+ x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.OneToOne) &&
+ x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(AppEntityWithNavigationChildOneToOne).FullName));
+#pragma warning restore 4014
+
+ using (var scope = _auditingManager.BeginScope())
+ {
+ using (var uow = _unitOfWorkManager.Begin())
+ {
+ var entity = await repository.GetAsync(entityId);
+
+ entity.OneToMany = new List()
+ {
+ new AppEntityWithNavigationChildOneToMany
+ {
+ AppEntityWithNavigationId = entity.Id,
+ ChildName = "ChildName1"
+ }
+ };
+
+ await repository.UpdateAsync(entity);
+ await uow.CompleteAsync();
+ await scope.SaveAsync();
+ }
+ }
+
+#pragma warning disable 4014
+ AuditingStore.Received().SaveAsync(Arg.Is(x => x.EntityChanges.Count == 2 &&
+ x.EntityChanges[0].ChangeType == EntityChangeType.Created &&
+ x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithNavigationChildOneToMany).FullName &&
+ x.EntityChanges[1].ChangeType == EntityChangeType.Updated &&
+ x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigations).FullName &&
+ x.EntityChanges[1].PropertyChanges.Count == 1 &&
+ x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.OneToMany) &&
+ x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(List).FullName));
+
+#pragma warning restore 4014
+
+ using (var scope = _auditingManager.BeginScope())
+ {
+ using (var uow = _unitOfWorkManager.Begin())
+ {
+ var entity = await repository.GetAsync(entityId);
+
+ entity.ManyToMany = new List()
+ {
+ new AppEntityWithNavigationChildManyToMany
+ {
+ ChildName = "ChildName1"
+ }
+ };
+
+ await repository.UpdateAsync(entity);
+ await uow.CompleteAsync();
+ await scope.SaveAsync();
+ }
+ }
+
+#pragma warning disable 4014
+ AuditingStore.Received().SaveAsync(Arg.Is(x => x.EntityChanges.Count == 2 &&
+ x.EntityChanges[0].ChangeType == EntityChangeType.Created &&
+ x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithNavigationChildManyToMany).FullName &&
x.EntityChanges[1].ChangeType == EntityChangeType.Updated &&
- x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithValueObjectAddress).FullName &&
+ x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigations).FullName &&
x.EntityChanges[1].PropertyChanges.Count == 1 &&
- x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithValueObjectAddress.Country) &&
- x.EntityChanges[1].PropertyChanges[0].OriginalValue == "\"England\"" &&
- x.EntityChanges[1].PropertyChanges[0].NewValue == "\"Germany\""));
+ x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.ManyToMany) &&
+ x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(List).FullName));
#pragma warning restore 4014
}
diff --git a/latest-versions.json b/latest-versions.json
index 7823e6af9d..e2911011e7 100644
--- a/latest-versions.json
+++ b/latest-versions.json
@@ -1,6 +1,6 @@
[
{
- "version": "7.4.0",
+ "version": "7.4.1",
"releaseDate": "",
"type": "stable",
"message": ""
diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs
index ab5e2819cb..d4bb0dbc5e 100644
--- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs
+++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs
@@ -242,14 +242,16 @@ public class LoginModel : AccountPageModel
var user = await UserManager.FindByEmailAsync(email);
if (user == null)
{
- user = await CreateExternalUserAsync(loginInfo);
+ return RedirectToPage("./Register", new {
+ IsExternalLogin = true,
+ ExternalLoginAuthSchema = loginInfo.LoginProvider,
+ ReturnUrl = returnUrl
+ });
}
- else
+
+ if (await UserManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey) == null)
{
- if (await UserManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey) == null)
- {
- CheckIdentityErrors(await UserManager.AddLoginAsync(user, loginInfo));
- }
+ CheckIdentityErrors(await UserManager.AddLoginAsync(user, loginInfo));
}
await SignInManager.SignInAsync(user, false);
@@ -264,35 +266,6 @@ public class LoginModel : AccountPageModel
return RedirectSafely(returnUrl, returnUrlHash);
}
- protected virtual async Task CreateExternalUserAsync(ExternalLoginInfo info)
- {
- await IdentityOptions.SetAsync();
-
- var emailAddress = info.Principal.FindFirstValue(AbpClaimTypes.Email) ?? info.Principal.FindFirstValue(ClaimTypes.Email);
- var userName = await GetUserNameFromEmail(emailAddress);
-
- var user = new IdentityUser(GuidGenerator.Create(), userName, emailAddress, CurrentTenant.Id);
-
- CheckIdentityErrors(await UserManager.CreateAsync(user));
- CheckIdentityErrors(await UserManager.SetEmailAsync(user, emailAddress));
- CheckIdentityErrors(await UserManager.AddLoginAsync(user, info));
- CheckIdentityErrors(await UserManager.AddDefaultRolesAsync(user));
-
- user.Name = info.Principal.FindFirstValue(AbpClaimTypes.Name);
- user.Surname = info.Principal.FindFirstValue(AbpClaimTypes.SurName);
-
- var phoneNumber = info.Principal.FindFirstValue(AbpClaimTypes.PhoneNumber);
- if (!phoneNumber.IsNullOrWhiteSpace())
- {
- var phoneNumberConfirmed = string.Equals(info.Principal.FindFirstValue(AbpClaimTypes.PhoneNumberVerified), "true", StringComparison.InvariantCultureIgnoreCase);
- user.SetPhoneNumber(phoneNumber, phoneNumberConfirmed);
- }
-
- await UserManager.UpdateAsync(user);
-
- return user;
- }
-
protected virtual async Task ReplaceEmailToUsernameOfInputIfNeeds()
{
if (!ValidationHelper.IsValidEmailAddress(LoginInput.UserNameOrEmailAddress))
diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml
index fabe41ae6f..745368402c 100644
--- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml
+++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml
@@ -12,7 +12,7 @@
@L["Login"]