Browse Source

Merge remote-tracking branch 'abpframework/dev' into docs

pull/4301/head
liangshiwei 6 years ago
parent
commit
f87158eb23
  1. 3
      docs/en/Blob-Storing-Azure.md
  2. 177
      docs/en/Blob-Storing-Custom-Provider.md
  3. 96
      docs/en/Blob-Storing-Database.md
  4. 59
      docs/en/Blob-Storing-File-System.md
  5. 306
      docs/en/Blob-Storing.md
  6. 2
      docs/en/Blog-Posts/2020-06-05 v2_9_Release/Post.md
  7. 1
      docs/en/CLI.md
  8. 2
      docs/en/Connection-Strings.md
  9. 166
      docs/en/CurrentUser.md
  10. 5
      docs/en/How-To/Customize-Login-Page-MVC.md
  11. 4
      docs/en/Index.md
  12. 21
      docs/en/Startup-Templates/Console.md
  13. 4
      docs/en/Startup-Templates/Index.md
  14. 21
      docs/en/Tutorials/Part-1.md
  15. 22
      docs/en/Tutorials/Part-2.md
  16. 4
      docs/en/UI/Angular/Component-Replacement.md
  17. 500
      docs/en/UI/Angular/Permission-Management-Component-Replacement.md
  18. BIN
      docs/en/UI/Angular/images/permission-management-modal.png
  19. 6
      docs/en/UI/AspNetCore/Customization-User-Interface.md
  20. 40
      docs/en/UI/AspNetCore/Tag-Helpers/Badges.md
  21. 126
      docs/en/UI/AspNetCore/Tag-Helpers/Borders.md
  22. 25
      docs/en/UI/AspNetCore/Tag-Helpers/Breadcrumbs.md
  23. 114
      docs/en/UI/AspNetCore/Tag-Helpers/Navs.md
  24. 61
      docs/en/UI/AspNetCore/Tag-Helpers/Tables.md
  25. 10
      docs/en/Virtual-File-System.md
  26. 38
      docs/en/docs-nav.json
  27. 11
      docs/pt-BR/Tutorials/AspNetCore-Mvc/Part-I.md
  28. 8
      docs/pt-BR/Tutorials/AspNetCore-Mvc/Part-II.md
  29. 5
      docs/zh-Hans/How-To/Customize-Login-Page-MVC.md
  30. 15
      docs/zh-Hans/Tutorials/Part-1.md
  31. 10
      docs/zh-Hans/Tutorials/Part-2.md
  32. 6
      docs/zh-Hans/UI/AspNetCore/Customization-User-Interface.md
  33. 10
      docs/zh-Hans/Virtual-File-System.md
  34. 30
      framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/RazorPages/AbpPageModel.cs
  35. 24
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpController.cs
  36. 2
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpViewComponent.cs
  37. 30
      framework/src/Volo.Abp.AspNetCore.SignalR/Volo/Abp/AspNetCore/SignalR/AbpHub.cs
  38. 2
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/HttpContextCurrentPrincipalAccessor.cs
  39. 4
      framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/BackgroundWorkerBase.cs
  40. 1
      framework/src/Volo.Abp.BlobStoring.FileSystem/Volo.Abp.BlobStoring.FileSystem.csproj
  41. 53
      framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/FileSystemBlobProvider.cs
  42. 5
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerFactory.cs
  43. 10
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerFactoryExtensions.cs
  44. 4
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/DefaultContainer.cs
  45. 5
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobContainerConfigurationProvider.cs
  46. 8
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobContainerFactory.cs
  47. 1
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs
  48. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs
  49. 3
      framework/src/Volo.Abp.Core/System/IO/AbpStreamExtensions.cs
  50. 24
      framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs
  51. 12
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Services/DomainService.cs
  52. 2
      framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/ThreadCurrentPrincipalAccessor.cs
  53. 4
      framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json
  54. 6
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Borders.cshtml
  55. 4
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Breadcrumbs.cshtml
  56. 2
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Carousel.cshtml
  57. 2
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Tables.cshtml
  58. 13
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/AccountPage.cs
  59. 4
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml
  60. 3
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/Logout.cshtml
  61. 6
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml
  62. 4
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml
  63. 6
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/SendSecurityCode.cshtml
  64. 55
      modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/DatabaseBlobProvider.cs
  65. 2
      modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/IDatabaseBlobRepository.cs
  66. 10
      modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/Volo/Abp/BlobStoring/Database/EntityFrameworkCore/EfCoreDatabaseBlobRepository.cs
  67. 8
      modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo/Abp/BlobStoring/Database/MongoDB/MongoDbDatabaseBlobRepository.cs
  68. 2
      modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.TestBase/Security/FakeCurrentPrincipalAccessor.cs
  69. 4
      modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/Create.cshtml
  70. 6
      modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/Edit.cshtml
  71. 4
      modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/Index.cshtml
  72. 14
      modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/BloggingPage.cs
  73. 4
      modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Index.cshtml
  74. 15
      modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Detail.cshtml
  75. 20
      modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Edit.cshtml
  76. 18
      modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Index.cshtml
  77. 4
      modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/New.cshtml
  78. 13
      modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/DocsAdminPage.cs
  79. 6
      modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Create.cshtml
  80. 6
      modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Edit.cshtml
  81. 4
      modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Index.cshtml
  82. 4
      modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Pull.cshtml
  83. 5
      modules/docs/src/Volo.Docs.Application/Volo/Docs/DocsApplicationModule.cs
  84. 74
      modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs
  85. 9
      modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/INavigationTreePostProcessor.cs
  86. 14
      modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/NavigationTreePostProcessorContext.cs
  87. 20
      modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/NullNavigationTreePostProcessor.cs
  88. 5
      modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hant.json
  89. 25
      modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs
  90. 6
      modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs
  91. 6
      modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs
  92. 28
      npm/ng-packs/package.json
  93. 2
      npm/ng-packs/packages/theme-shared/package.json
  94. 28
      npm/ng-packs/packages/theme-shared/src/lib/handlers/lazy-style.handler.ts
  95. 139
      npm/ng-packs/yarn.lock
  96. 2
      samples/MicroserviceDemo/gateways/PublicWebSiteGateway.Host/PublicWebSiteGateway.Host.csproj
  97. 3
      samples/MicroserviceDemo/gateways/PublicWebSiteGateway.Host/PublicWebSiteGatewayHostModule.cs
  98. 25
      samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Pages/ProductManagement/ProductManagementPage.cs
  99. 4
      samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Pages/ProductManagement/Products/Create.cshtml
  100. 6
      samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Pages/ProductManagement/Products/Edit.cshtml

3
docs/en/Blob-Storing-Azure.md

@ -0,0 +1,3 @@
# BLOB Storing Azure Provider
This feature will be available with v3.0!

177
docs/en/Blob-Storing-Custom-Provider.md

@ -0,0 +1,177 @@
# BLOB Storing: Creating a Custom Provider
This document explains how you can create a new storage provider for the BLOB storing system with an example.
> Read the [BLOB Storing document](Blob-Storing.md) to understand how to use the BLOB storing system. This document only covers how to create a new storage provider.
## Example Implementation
The first step is to create a class implements the `IBlobProvider` interface or inherit from the `BlobProviderBase` abstract class.
````csharp
using System.IO;
using System.Threading.Tasks;
using Volo.Abp.BlobStoring;
using Volo.Abp.DependencyInjection;
namespace AbpDemo
{
public class MyCustomBlobProvider : BlobProviderBase, ITransientDependency
{
public override Task SaveAsync(BlobProviderSaveArgs args)
{
//TODO...
}
public override Task<bool> DeleteAsync(BlobProviderDeleteArgs args)
{
//TODO...
}
public override Task<bool> ExistsAsync(BlobProviderExistsArgs args)
{
//TODO...
}
public override Task<Stream> GetOrNullAsync(BlobProviderGetArgs args)
{
//TODO...
}
}
}
````
* `MyCustomBlobProvider` inherits from the `BlobProviderBase` and overrides the `abstract` methods. The actual implementation is up to you.
* Implementing `ITransientDependency` registers this class to the [Dependency Injection](Dependency-Injection.md) system as a transient service.
> **Notice: Naming conventions are important**. If your class name doesn't end with `BlobProvider`, you must manually register/expose your service for the `IBlobProvider`.
That's all. Now, you can configure containers (inside the `ConfigureServices` method of your [module](Module-Development-Basics.md)) to use the `MyCustomBlobProvider` class:
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.ProviderType = typeof(MyCustomBlobProvider);
});
});
````
> See the [BLOB Storing document](Blob-Storing.md) if you want to configure a specific container.
### BlobContainerConfiguration Extension Method
If you want to provide a simpler configuration, create an extension method for the `BlobContainerConfiguration` class:
````
public static class MyBlobContainerConfigurationExtensions
{
public static BlobContainerConfiguration UseMyCustomBlobProvider(
this BlobContainerConfiguration containerConfiguration)
{
containerConfiguration.ProviderType = typeof(MyCustomBlobProvider);
return containerConfiguration;
}
}
````
Then you can configure containers easier using the extension method:
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.UseMyCustomBlobProvider();
});
});
````
### Extra Configuration Options
`BlobContainerConfiguration` allows to add/remove provider specific configuration objects. If your provider needs to additional configuration, you can create a wrapper class to the `BlobContainerConfiguration` for a type-safe configuration option:
````csharp
public class MyCustomBlobProviderConfiguration
{
public string MyOption1
{
get => _containerConfiguration
.GetConfiguration<string>("MyCustomBlobProvider.MyOption1");
set => _containerConfiguration
.SetConfiguration("MyCustomBlobProvider.MyOption1", value);
}
private readonly BlobContainerConfiguration _containerConfiguration;
public MyCustomBlobProviderConfiguration(
BlobContainerConfiguration containerConfiguration)
{
_containerConfiguration = containerConfiguration;
}
}
````
Then you can change the `MyBlobContainerConfigurationExtensions` class like that:
````csharp
public static class MyBlobContainerConfigurationExtensions
{
public static BlobContainerConfiguration UseMyCustomBlobProvider(
this BlobContainerConfiguration containerConfiguration,
Action<MyCustomBlobProviderConfiguration> configureAction)
{
containerConfiguration.ProviderType = typeof(MyCustomBlobProvider);
configureAction.Invoke(
new MyCustomBlobProviderConfiguration(containerConfiguration)
);
return containerConfiguration;
}
public static MyCustomBlobProviderConfiguration GetMyCustomBlobProviderConfiguration(
this BlobContainerConfiguration containerConfiguration)
{
return new MyCustomBlobProviderConfiguration(containerConfiguration);
}
}
````
* Added an action parameter to the `UseMyCustomBlobProvider` method to allow developers to set the additional options.
* Added a new `GetMyCustomBlobProviderConfiguration` method to be used inside `MyCustomBlobProvider` class to obtain the configured values.
Then anyone can set the `MyOption1` as shown below:
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.UseMyCustomBlobProvider(provider =>
{
provider.MyOption1 = "my value";
});
});
});
````
Finally, you can access to the extra options using the `GetMyCustomBlobProviderConfiguration` method:
````csharp
public class MyCustomBlobProvider : BlobProviderBase, ITransientDependency
{
public override Task SaveAsync(BlobProviderSaveArgs args)
{
var config = args.Configuration.GetMyCustomBlobProviderConfiguration();
var value = config.MyOption1;
//...
}
}
````
## Contribute?
If you create a new provider and you think it can be useful for other developers, please consider to [contribute](Contribution/Index.md) to the ABP Framework on GitHub.

96
docs/en/Blob-Storing-Database.md

@ -0,0 +1,96 @@
# BLOB Storing Database Provider
BLOB Storing Database Storage Provider can store BLOBs in a relational or non-relational database.
There are two database providers implemented;
* [Volo.Abp.BlobStoring.Database.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.EntityFrameworkCore) package implements for [EF Core](Entity-Framework-Core.md), so it can store BLOBs in [any DBMS supported](https://docs.microsoft.com/en-us/ef/core/providers/) by the EF Core.
* [Volo.Abp.BlobStoring.Database.MongoDB](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.MongoDB) package implements for [MongoDB](MongoDB.md).
> Read the [BLOB Storing document](Blob-Storing.md) to understand how to use the BLOB storing system. This document only covers how to configure containers to use a database as the storage provider.
## Installation
### Automatic Installation
If you've created your solution based on the [application startup template](Startup-Templates/Application.md), you can use the `abp add-module` [CLI](CLI.md) command to automatically add related packages to your solution.
Open a command prompt (terminal) in the folder containing your solution (`.sln`) file and run the following command:
````bash
abp add-module Volo.Abp.BlobStoring.Database
````
This command adds all the NuGet packages to corresponding layers of your solution. If you are using EF Core, it adds necessary configuration, adds a new database migration and updates the database.
### Manual Installation
Here, all the NuGet packages defined by this provider;
* [Volo.Abp.BlobStoring.Database.Domain.Shared](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Domain.Shared)
* [Volo.Abp.BlobStoring.Database.Domain](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.Domain)
* [Volo.Abp.BlobStoring.Database.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.EntityFrameworkCore)
* [Volo.Abp.BlobStoring.Database.MongoDB](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.MongoDB)
You can only install Volo.Abp.BlobStoring.Database.EntityFrameworkCore or Volo.Abp.BlobStoring.Database.MongoDB (based on your preference) since they depends on the other packages.
After installation, add `DepenedsOn` attribute to your related [module](Module-Development-Basics.md). Here, the list of module classes defined by the related NuGet packages listed above:
* `BlobStoringDatabaseDomainModule`
* `BlobStoringDatabaseDomainSharedModule`
* `BlobStoringDatabaseEntityFrameworkCoreModule`
* `BlobStoringDatabaseMongoDbModule`
Whenever you add a NuGet package to a project, also add the module class dependency.
If you are using EF Core, you also need to configure your **Migration DbContext** to add BLOB storage tables to your database schema. Call `builder.ConfigureBlobStoring()` extension method inside the `OnModelCreating` method to include mappings to your DbContext. Then you can use the standard `Add-Migration` and `Update-Database` [commands](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/) to create necessary tables in your database.
## Configuration
### Connection String
If you will use your `Default` connection string, you don't need to any additional configuration.
If you want to use a separate database for BLOB storage, use the `AbpBlobStoring` as the [connection string](Connection-Strings.md) name in your configuration file (`appsettings.json`). In this case, also read the [EF Core Migrations](Entity-Framework-Core-Migrations.md) document to learn how to create and use a different database for a desired module.
### Configuring the Containers
Configuration is done in the `ConfigureServices` method of your [module](Module-Development-Basics.md) class, as explained in the [BLOB Storing document](Blob-Storing.md).
**Example: Configure to use the database storage provider by default**
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.UseDatabase();
});
});
````
> See the [BLOB Storing document](Blob-Storing.md) to learn how to configure this provider for a specific container.
## Additional Information
It is expected to use the [BLOB Storing services](Blob-Storing.md) to use the BLOB storing system. However, if you want to work on the database tables/entities, you can use the following information.
### Entities
Entities defined for this module:
* `DatabaseBlobContainer` (aggregate root) represents a container stored in the database.
* `DatabaseBlob` (aggregate root) represents a BLOB in the database.
See the [entities document](Entities.md) to learn what is an entity and aggregate root.
### Repositories
* `IDatabaseBlobContainerRepository`
* `IDatabaseBlobRepository`
You can also use `IRepository<DatabaseBlobContainer, Guid>` and `IRepository<DatabaseBlob, Guid>` to take the power of IQueryable. See the [repository document](Repositories.md) for more.
### Other Services
* `DatabaseBlobProvider` is the main service that implements the database BLOB storage provider, if you want to override/replace it via [dependency injection](Dependency-Injection.md) (don't replace `IBlobProvider` interface, but replace `DatabaseBlobProvider` class).

59
docs/en/Blob-Storing-File-System.md

@ -0,0 +1,59 @@
# BLOB Storing File System Provider
File System Storage Provider is used to store BLOBs in the local file system as standard files inside a folder.
> Read the [BLOB Storing document](Blob-Storing.md) to understand how to use the BLOB storing system. This document only covers how to configure containers to use the file system.
## Installation
Use the ABP CLI to add [Volo.Abp.BlobStoring.FileSystem](https://www.nuget.org/packages/Volo.Abp.BlobStoring.FileSystem) NuGet package to your project:
* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed before.
* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.BlobStoring.FileSystem` package.
* Run `abp add-package Volo.Abp.BlobStoring.FileSystem` command.
If you want to do it manually, install the [Volo.Abp.BlobStoring.FileSystem](https://www.nuget.org/packages/Volo.Abp.BlobStoring.FileSystem) NuGet package to your project and add `[DependsOn(typeof(AbpBlobStoringFileSystemModule))]` to the [ABP module](Module-Development-Basics.md) class inside your project.
## Configuration
Configuration is done in the `ConfigureServices` method of your [module](Module-Development-Basics.md) class, as explained in the [BLOB Storing document](Blob-Storing.md).
**Example: Configure to use the File System storage provider by default**
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.UseFileSystem(fileSystem =>
{
fileSystem.BasePath = "C:\\my-files";
});
});
});
````
`UseFileSystem` extension method is used to set the File System Provider for a container and configure the file system options.
> See the [BLOB Storing document](Blob-Storing.md) to learn how to configure this provider for a specific container.
### Options
* **BasePath** (string): The base folder path to store BLOBs. It is required to set this option.
* **AppendContainerNameToBasePath** (bool; default: `true`): Indicates whether to create a folder with the container name inside the base folder. If you store multiple containers in the same `BaseFolder`, leave this as `true`. Otherwise, you can set it to `false` if you don't like an unnecessarily deeper folder hierarchy.
## File Path Calculation
File System Provider organizes BLOB files inside folders and implements some conventions. The full path of a BLOB file is determined by the following rules by default:
* It starts with the `BasePath` configured as shown above.
* Appends `host` folder if [current tenant](Multi-Tenancy.md) is `null` (or multi-tenancy is disabled for the container - see the [BLOB Storing document](Blob-Storing.md) to learn how to disable multi-tenancy for a container).
* Appends `tenants/<tenant-id>` folder if current tenant is not `null`.
* Appends the container's name if `AppendContainerNameToBasePath` is `true`. If container name contains `/`, this will result with nested folders.
* Appends the BLOB name. If the BLOB name contains `/` it creates folders. If the BLOB name contains `.` it will have a file extension.
## Extending the File System BLOB Provider
* `FileSystemBlobProvider` is the main service that implements the File System storage. You can inherit from this class and [override](Customizing-Application-Modules-Overriding-Services.md) methods to customize it.
* The `IBlobFilePathCalculator` service is used to calculate the file paths. Default implementation is the `DefaultBlobFilePathCalculator`. You can replace/override it if you want to customize the file path calculation.

306
docs/en/Blob-Storing.md

@ -1,3 +1,305 @@
# Blog Storing
# BLOB Storing
TODO
It is typical to **store file contents** in an application and read these file contents on need. Not only files, but you may also need to save various types of **large binary objects**, a.k.a. [BLOB](https://en.wikipedia.org/wiki/Binary_large_object)s, into a **storage**. For example, you may want to save user profile pictures.
A BLOB is a typically **byte array**. There are various places to store a BLOB item; storing in the local file system, in a shared database or on the [Azure BLOB storage](https://azure.microsoft.com/en-us/services/storage/blobs/) can be options.
The ABP Framework provides an abstraction to work with BLOBs and provides some pre-built storage providers that you can easily integrate to. Having such an abstraction has some benefits;
* You can **easily integrate** to your favorite BLOB storage provides with a few lines of configuration.
* You can then **easily change** your BLOB storage without changing your application code.
* If you want to create **reusable application modules**, you don't need to make assumption about how the BLOBs are stored.
ABP BLOB Storage system is also compatible to other ABP Framework features like [multi-tenancy](Multi-Tenancy.md).
## BLOB Storage Providers
The ABP Framework has already the following storage provider implementations;
* [File System](Blob-Storing-File-System.md): Stores BLOBs in a folder of the local file system, as standard files.
* [Database](Blob-Storing-Database.md): Stores BLOBs in a database.
* [Azure](Blob-Storing-Azure.md): Stores BLOBs on the [Azure BLOB storage](https://azure.microsoft.com/en-us/services/storage/blobs/).
More providers will be implemented by the time. You can [request](https://github.com/abpframework/abp/issues/new) it for your favorite provider or [create it yourself](Blob-Storing-Custom-Provider.md) and [contribute](Contribution/Index.md) to the ABP Framework.
Multiple providers **can be used together** by the help of the **container system**, where each container can uses a different provider.
> BLOB storing system can not work unless you **configure a storage provider**. Refer to the linked documents for the storage provider configurations.
## Installation
[Volo.Abp.BlobStoring](https://www.nuget.org/packages/Volo.Abp.BlobStoring) is the main package that defines the BLOB storing services. You can use this package to use the BLOB Storing system without depending a specific storage provider.
Use the ABP CLI to add this package to your project:
* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI), if you haven't installed it.
* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.BlobStoring` package.
* Run `abp add-package Volo.Abp.BlobStoring` command.
If you want to do it manually, install the [Volo.Abp.BlobStoring](https://www.nuget.org/packages/Volo.Abp.BlobStoring) NuGet package to your project and add `[DependsOn(typeof(AbpBlobStoringModule))]` to the [ABP module](Module-Development-Basics.md) class inside your project.
## The IBlobContainer
`IBlobContainer` is the main interface to store and read BLOBs. Your application may have multiple containers and each container can be separately configured. But, there is a **default container** that can be simply used by [injecting](Dependency-Injection.md) the `IBlobContainer`.
**Example: Simply save and read bytes of a named BLOB**
````csharp
using System.Threading.Tasks;
using Volo.Abp.BlobStoring;
using Volo.Abp.DependencyInjection;
namespace AbpDemo
{
public class MyService : ITransientDependency
{
private readonly IBlobContainer _blobContainer;
public MyService(IBlobContainer blobContainer)
{
_blobContainer = blobContainer;
}
public async Task SaveBytesAsync(byte[] bytes)
{
await _blobContainer.SaveAsync("my-blob-1", bytes);
}
public async Task<byte[]> GetBytesAsync()
{
return await _blobContainer.GetAllBytesOrNullAsync("my-blob-1");
}
}
}
````
This service saves the given bytes with the `my-blob-1` name and then gets the previously saved bytes with the same name.
> A BLOB is a named object and **each BLOB should have a unique name**, which is an arbitrary string.
`IBlobContainer` can work with `Stream` and `byte[]` objects, which will be detailed in the next sections.
### Saving BLOBs
`SaveAsync` method is used to save a new BLOB or replace an existing BLOB. It can save a `Stream` by default, but there is a shortcut extension method to save byte arrays.
`SaveAsync` gets the following parameters:
* **name** (string): Unique name of the BLOB.
* **stream** (Stream) or **bytes** (byte[]): The stream to read the BLOB content or a byte array.
* **overrideExisting** (bool): Set `true` to replace the BLOB content if it does already exists. Default value is `false` and throws `BlobAlreadyExistsException` if there is already a BLOB in the container with the same name.
### Reading/Getting BLOBs
* `GetAsync`: Only gets a BLOB name and returns a `Stream` object that can be used to read the BLOB content. Always **dispose the stream** after using it. This method throws exception, if it can not find the BLOB with the given name.
* `GetOrNullAsync`: In opposite to the `GetAsync` method, this one returns `null` if there is no BLOB found with the given name.
* `GetAllBytesAsync`: Returns a `byte[]` instead of a `Stream`. Still throws exception if can not find the BLOB with the given name.
* `GetAllBytesOrNullAsync`: In opposite to the `GetAllBytesAsync` method, this one returns `null` if there is no BLOB found with the given name.
### Deleting BLOBs
`DeleteAsync` method gets a BLOB name and deletes the BLOB data. It doesn't throw any exception if given BLOB was not found. Instead, it returns a `bool` indicating that the BLOB was actually deleted or not, if you care about it.
### Other Methods
* `ExistsAsync` method simply checks if there is a BLOB in the container with the given name.
### About Naming the BLOBs
There is not a rule for naming the BLOBs. A BLOB name is just a string that is unique per container (and per tenant - see the "*Multi-Tenancy*" section). However, different storage providers may conventionally implement some practices. For example, the [File System Provider](Blob-Storing-File-System.md) use directory separators (`/`) and file extensions in your BLOB name (if your BLOB name is `images/common/x.png` then it is saved as `x.png` in the `images/common` folder inside the root container folder).
## Typed IBlobContainer
Typed BLOB container system is a way of creating and managing **multiple containers** in an application;
* **Each container is separately stored**. That means the BLOB names should be unique in a container and two BLOBs with the same name can live in different containers without effecting each other.
* **Each container can be separately configured**, so each container can use a different storage provider based on your configuration.
To create a typed container, you need to create a simple class decorated with the `BlobContainerName` attribute:
````csharp
using Volo.Abp.BlobStoring;
namespace AbpDemo
{
[BlobContainerName("profile-pictures")]
public class ProfilePictureContainer
{
}
}
````
> If you don't use the `BlobContainerName` attribute, ABP Framework uses the full name of the class (with namespace), but it is always recommended to use a container name which is stable and does not change even if you rename the class.
Once you create the container class, you can inject `IBlobContainer<T>` for your container type.
**Example: An [application service](Application-Services.md) to save and read profile picture of the [current user](CurrentUser.md)**
````csharp
[Authorize]
public class ProfileAppService : ApplicationService
{
private readonly IBlobContainer<ProfilePictureContainer> _blobContainer;
public ProfileAppService(IBlobContainer<ProfilePictureContainer> blobContainer)
{
_blobContainer = blobContainer;
}
public async Task SaveProfilePictureAsync(byte[] bytes)
{
var blobName = CurrentUser.GetId().ToString();
await _blobContainer.SaveAsync(blobName, bytes);
}
public async Task<byte[]> GetProfilePictureAsync()
{
var blobName = CurrentUser.GetId().ToString();
return await _blobContainer.GetAllBytesOrNullAsync(blobName);
}
}
````
`IBlobContainer<T>` has the same methods with the `IBlobContainer`.
> It is a good practice to **always use a typed container while developing re-usable modules**, so the final application can configure the provider for your container without effecting the other containers.
### The Default Container
If you don't use the generic argument and directly inject the `IBlobContainer` (as explained before), you get the default container. Another way of injecting the default container is using `IBlobContainer<DefaultContainer>`, which returns exactly the same container.
The name of the default container is `Default`.
### Named Containers
Typed containers are just shortcuts for named containers. You can inject and use the `IBlobContainerFactory` to get a BLOB container by its name:
````csharp
public class ProfileAppService : ApplicationService
{
private readonly IBlobContainer _blobContainer;
public ProfileAppService(IBlobContainerFactory blobContainerFactory)
{
_blobContainer = blobContainerFactory.Create("profile-pictures");
}
//...
}
````
## IBlobContainerFactory
`IBlobContainerFactory` is the service that is used to create the BLOB containers. One example was shown above.
**Example: Create a container by name**
````csharp
var blobContainer = blobContainerFactory.Create("profile-pictures");
````
**Example: Create a container by type**
````csharp
var blobContainer = blobContainerFactory.Create<ProfilePictureContainer>();
````
> You generally don't need to use the `IBlobContainerFactory` since it is used internally, when you inject a `IBlobContainer` or `IBlobContainer<T>`.
## Configuring the Containers
Containers should be configured before using them. The most fundamental configuration is to **select a BLOB storage provider** (see the "*BLOB Storage Providers*" section above).
`AbpBlobStoringOptions` is the [options class](Options.md) to configure the containers. You can configure the options inside the `ConfigureServices` method of your [module](Module-Development-Basics.md).
### Configure a Single Container
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.Configure<ProfilePictureContainer>(container =>
{
//TODO...
});
});
````
This example configures the `ProfilePictureContainer`. You can also configure by the container name:
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.Configure("profile-pictures", container =>
{
//TODO...
});
});
````
### Configure the Default Container
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
//TODO...
});
});
````
> There is a special case about the default container; If you don't specify a configuration for a container, it **fallbacks to the default container configuration**. This is a good way to configure defaults for all containers and specialize configuration for a specific container when needed.
### Configure All Containers
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureAll((containerName, containerConfiguration) =>
{
//TODO...
});
});
````
This is a way to configure all the containers.
> The main difference from configuring the default container is that `ConfigureAll` overrides the configuration even if it was specialized for a specific container.
## Multi-Tenancy
If your application is set as multi-tenant, the BLOB Storage system **works seamlessly with the [multi-tenancy](Multi-Tenancy.md)**. All the providers implement multi-tenancy as a standard feature. They **isolate BLOBs** of different tenants from each other, so they can only access to their own BLOBs. It means you can use the **same BLOB name for different tenants**.
If your application is multi-tenant, you may want to control **multi-tenancy behavior** of the containers individually. For example, you may want to **disable multi-tenancy** for a specific container, so the BLOBs inside it will be **available to all the tenants**. This is a way to share BLOBs among all tenants.
**Example: Disable multi-tenancy for a specific container**
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.Configure<ProfilePictureContainer>(container =>
{
container.IsMultiTenant = false;
});
});
````
> If your application is not multi-tenant, no worry, it works as expected. You don't need to configure the `IsMultiTenant` option.
## Extending the BLOB Storing System
Most of the times, you won't need to customize the BLOB storage system except [creating a custom BLOB storage provider](Blob-Storing-Custom-Provider.md). However, you can replace any service (injected via [dependency injection](Dependency-Injection.md)), if you need. Here, some other services not mentioned above, but you may want to know:
* `IBlobProviderSelector` is used to get a `IBlobProvider` instance by a container name. Default implementation (`DefaultBlobProviderSelector`) selects the provider using the configuration.
* `IBlobContainerConfigurationProvider` is used to get the `BlobContainerConfiguration` for a given container name. Default implementation (`DefaultBlobContainerConfigurationProvider`) gets the configuration from the `AbpBlobStoringOptions` explained above.
## BLOB Storing vs File Management System
Notice that BLOB storing is not a file management system. It is a low level system that is used to save, get and delete named BLOBs. It doesn't provide a hierarchical structure like directories, you may expect from a typical file system.
If you want to create folders and move files between folders, assign permissions to files and share files between users then you need to implement your own application on top of the BLOB Storage system.
## See Also
* [Creating a custom BLOB storage provider](Blob-Storing-Custom-Provider.md)

2
docs/en/Blog-Posts/2020-06-05 v2_9_Release/Post.md

@ -224,6 +224,8 @@ We've created the UI for manage organization units, their members and roles for
OU management is available for both of the MVC (Razor Pages) and the Angular user interfaces.
> See [this entry](https://support.abp.io/QA/Questions/222/Bugs--Problems-v290#answer-3cf5eba3-0bf1-2aa1-cc5e-39f5a0750329) if you're upgrading your solution from an earlier version.
### Chat Module Angular UI
We had introduced a new [chat module](https://commercial.abp.io/modules/Volo.Chat) in the previous version, which was only supporting the ASP.NET Core MVC / Razor Pages UI. Now, it has also an Angular UI option.

1
docs/en/CLI.md

@ -87,6 +87,7 @@ abp new Acme.BookStore
* `mongodb`: MongoDB.
* **`module`**: [Module template](Startup-Templates/Module.md). Additional options:
* `--no-ui`: Specifies to not include the UI. This makes possible to create service-only modules (a.k.a. microservices - without UI).
* **`console`**: [Console template](Startup-Templates/Console.md).
* `--output-folder` or `-o`: Specifies the output folder. Default value is the current directory.
* `--version` or `-v`: Specifies the ABP & template version. It can be a [release tag](https://github.com/abpframework/abp/releases) or a [branch name](https://github.com/abpframework/abp/branches). Uses the latest release if not specified. Most of the times, you will want to use the latest version.
* `--template-source` or `-ts`: Specifies a custom template source to use to build the project. Local and network sources can be used(Like `D\localTemplate` or `https://<your url>.zip`).

2
docs/en/Connection-Strings.md

@ -72,7 +72,7 @@ Relational databases require to create the database and the database schema (tab
The startup template (with EF Core ORM) comes with a single database and a `.EntityFrameworkCore.DbMigrations` project that contains the migration files for that database. This project mainly defines a *YourProjectName*MigrationsDbContext that calls the `Configure...()` methods of the used modules, like `builder.ConfigurePermissionManagement()`.
Once you want to separate a module's database, you typically will need to create a second migration path. The easiest way to create a copy of the `.EntityFrameworkCore.DbMigrations` project with the `DbContext` inside it, change its content to only call the `Configure...()` methods of the modules needs to be stored in the second database and re-create the initial migration. In this case, you also need to change the `.DbMigrator` application to be able to work with these second database too. In this way, you will have a separate migrations DbContext per database.
Once you want to separate a module's database, you typically will need to create a second migration path. See the [EF Core Migrations](Entity-Framework-Core-Migrations.md) document to learn how to create and use a different database for a desired module.
## Multi-Tenancy

166
docs/en/CurrentUser.md

@ -1,3 +1,167 @@
# Current User
TODO!
It is very common to retrieve the information about the logged in user in a web application. The current user is the active user related to the current request in a web application.
## ICurrentUser
`ICurrentUser` is the main service to get info about the current active user.
Example: [Injecting](Dependency-Injection.md) the `ICurrentUser` into a service:
````csharp
using System;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Users;
namespace AbpDemo
{
public class MyService : ITransientDependency
{
private readonly ICurrentUser _currentUser;
public MyService(ICurrentUser currentUser)
{
_currentUser = currentUser;
}
public void Foo()
{
Guid? userId = _currentUser.Id;
}
}
}
````
Common base classes have already injected this service as a base property. For example, you can directly use the `CurrentUser` property in an [application service](Application-Services.md):
````csharp
using System;
using Volo.Abp.Application.Services;
namespace AbpDemo
{
public class MyAppService : ApplicationService
{
public void Foo()
{
Guid? userId = CurrentUser.Id;
}
}
}
````
### Properties
Here are the fundamental properties of the `ICurrentUser` interface:
* **IsAuthenticated** (bool): Returns `true` if the current user has logged in (authenticated). If the user has not logged in then `Id` and `UserName` returns `null`.
* **Id** (Guid?): Id of the current user. Returns `null`, if the current user has not logged in.
* **UserName** (string): User name of the current user. Returns `null`, if the current user has not logged in.
* **TenantId** (Guid?): Tenant Id of the current user, which can be useful for a [multi-tenant](Multi-Tenancy.md) application. Returns `null`, if the current user is not assigned to a tenant.
* **Email** (string): Email address of the current user.Returns `null`, if the current user has not logged in or not set an email address.
* **EmailVerified** (bool): Returns `true`, if the phone number of the current user has been verified.
* **PhoneNumber** (string): Phone number of the current user. Returns `null`, if the current user has not logged in or not set a phone number.
* **PhoneNumberVerified** (bool): Returns `true`, if the phone number of the current user has been verified.
* **Roles** (string[]): Roles of the current user. Returns a string array of the role names of the current user.
### Methods
`ICurrentUser` is implemented on the `ICurrentPrincipalAccessor` (see the section below) and works with the claims. So, all of the above properties are actually retrieved from the claims of the current authenticated user.
`ICurrentUser` has some methods to directly work with the claims, if you have custom claims or get other non-common claim types.
* **FindClaim**: Gets a claim with the given name. Returns `null` if not found.
* **FindClaims**: Gets all the claims with the given name (it is allowed to have multiple claim values with the same name).
* **GetAllClaims**: Gets all the claims.
* **IsInRole**: A shortcut method to check if the current user is in the specified role.
Beside these standard methods, there are some extension methods:
* **FindClaimValue**: Gets the value of the claim with the given name, or `null` if not found. It has a generic overload that also casts the value to a specific type.
* **GetId**: Returns `Id` of the current user. If the current user has not logged in, it throws an exception (instead of returning `null`) . Use this only if you are sure that the user has already authenticated in your code context.
### Authentication & Authorization
`ICurrentUser` works independently of how the user is authenticated or authorized. It seamlessly works with any authentication system that works with the current principal (see the section below).
## ICurrentPrincipalAccessor
`ICurrentPrincipalAccessor` is the service that should be used (by the ABP Framework and your application code) whenever the current principle of the current user is needed.
For a web application, it gets the `User` property of the current `HttpContext`. For a non-web application, it returns the `Thread.CurrentPrincipal`.
> You generally don't need to this low level `ICurrentPrincipalAccessor` service and directly work with the `ICurrentUser` explained above.
### Basic Usage
You can inject `ICurrentPrincipalAccessor` and use the `Principal` property to the the current principal:
````csharp
public class MyService : ITransientDependency
{
private readonly ICurrentPrincipalAccessor _currentPrincipalAccessor;
public MyService(ICurrentPrincipalAccessor currentPrincipalAccessor)
{
_currentPrincipalAccessor = currentPrincipalAccessor;
}
public void Foo()
{
var allClaims = _currentPrincipalAccessor.Principal.Claims.ToList();
//...
}
}
````
### Changing the Current Principle
Current principle is not something you want to set or change, except at some advanced scenarios. If you need it, use the `Change` method of the `ICurrentPrincipalAccessor`. It takes a `ClaimsPrinciple` object and makes it "current" for a scope.
Example:
````csharp
public class MyAppService : ApplicationService
{
private readonly ICurrentPrincipalAccessor _currentPrincipalAccessor;
public MyAppService(ICurrentPrincipalAccessor currentPrincipalAccessor)
{
_currentPrincipalAccessor = currentPrincipalAccessor;
}
public void Foo()
{
var newPrinciple = new ClaimsPrincipal(
new ClaimsIdentity(
new Claim[]
{
new Claim(AbpClaimTypes.UserId, Guid.NewGuid().ToString()),
new Claim(AbpClaimTypes.UserName, "john"),
new Claim("MyCustomCliam", "42")
}
)
);
using (_currentPrincipalAccessor.Change(newPrinciple))
{
var userName = CurrentUser.UserName; //returns "john"
//...
}
}
}
````
Use the `Change` method always in a `using` statement, so it will be restored to the original value after the `using` scope ends.
This can be a way to simulate a user login for a scope of the application code, however try to use it carefully.
## AbpClaimTypes
`AbpClaimTypes` is a static class that defines the names of the standard claims and used by the ABP Framework.
* Default values for the `UserName`, `UserId`, `Role` and `Email` properties are set from the [System.Security.Claims.ClaimTypes](https://docs.microsoft.com/en-us/dotnet/api/system.security.claims.claimtypes) class, but you can change them.
* Other properties, like `EmailVerified`, `PhoneNumber`, `TenantId`... are defined by the ABP Framework by following the standard names wherever possible.
It is suggested to use properties of this class instead of magic strings for claim names.

5
docs/en/How-To/Customize-Login-Page-MVC.md

@ -26,7 +26,7 @@ Then you can override any method you need and add new methods and properties nee
## Overriding the Login Page UI
Create folder named **Account** under **Pages** directory and create a **Login.cshtml** under this folder. It will automatically override the `Login.cshtml` file defined in the Account Module thanks to the [Virtual File System](../Virtual-File-System.md).
Create folder named **Account** under **Pages** directory and create a **Login.cshtml** under this folder. It will automatically override the `Login.cshtml` file defined in the Account Module.
A good way to customize a page is to copy its source code. [Click here](https://github.com/abpframework/abp/blob/dev/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml) for the source code of the login page. At the time this document has been written, the source code was like below:
@ -35,7 +35,6 @@ A good way to customize a page is to copy its source code. [Click here](https://
@using Volo.Abp.Account.Settings
@using Volo.Abp.Settings
@model Acme.BookStore.Web.Pages.Account.CustomLoginModel
@inherits Volo.Abp.Account.Web.Pages.Account.AccountPage
@inject Volo.Abp.Settings.ISettingProvider SettingProvider
@if (Model.EnableLocalLogin)
{
@ -110,4 +109,4 @@ You can find the source code of the completed example [here](https://github.com/
## See Also
* [ASP.NET Core (MVC / Razor Pages) User Interface Customization Guide](../UI/AspNetCore/Customization-User-Interface.md).
* [ASP.NET Core (MVC / Razor Pages) User Interface Customization Guide](../UI/AspNetCore/Customization-User-Interface.md).

4
docs/en/Index.md

@ -8,8 +8,8 @@ Explore the left navigation menu to deep dive in the documentation.
Easiest way to start a new project with ABP is to use the startup templates:
* [ASP.NET Core MVC (Razor Pages) UI Startup Template](Getting-Started?UI=MVC&DB=EF&Tiered=No)
* [Angular UI Startup Template](Getting-Started?UI=NG&DB=EF&Tiered=No)
* [ASP.NET Core MVC (Razor Pages) UI Startup Template](Getting-Started.md?UI=MVC&DB=EF&Tiered=No)
* [Angular UI Startup Template](Getting-Started.md?UI=NG&DB=EF&Tiered=No)
If you want to start from scratch (with an empty project) then manually install the ABP Framework and use the following tutorials:

21
docs/en/Startup-Templates/Console.md

@ -0,0 +1,21 @@
# Console Application Startup Template
This template is used to create a minimalist console application project.
## How to Start With?
First, install the [ABP CLI](../CLI.md) if you haven't installed before:
````bash
dotnet tool install -g Volo.Abp.Cli
````
Then use the `abp new` command in an empty folder to create a new solution:
````bash
abp new Acme.MyConsoleApp -t console
````
`Acme.MyConsoleApp` is the solution name, like *YourCompany.YourProduct*. You can use single level, two-levels or three-levels naming.
###

4
docs/en/Startup-Templates/Index.md

@ -4,6 +4,4 @@ While you can start with an empty project and add needed packages manually, star
* [**app**](Application.md): Application template.
* [**module**](Module.md): Module/service template.
* [**console**](Console.md): Console template.

21
docs/en/Tutorials/Part-1.md

@ -14,7 +14,7 @@ else if UI == "NG"
DB="mongodb"
DB_Text="MongoDB"
UI_Text="angular"
else
else
DB ="?"
UI_Text="?"
end
@ -26,7 +26,7 @@ In this tutorial series, you will build an ABP application named `Acme.BookStore
The ASP.NET Core {{UI_Value}} tutorial series consists of 3 parts:
- **Part-1: Creating the project and book list page (this tutorial)**
- **Part-1: Creating the project and book list page (this tutorial)**
- [Part-2: Creating, updating and deleting books](part-2.md)
- [Part-3: Integration tests](part-3.md)
@ -106,7 +106,7 @@ This is how the layered solution structure looks like:
![bookstore-visual-studio-solution](./images/bookstore-solution-structure-{{UI_Text}}.png)
Check out the [solution structure](../startup-templates/application#solution-structure) section to understand the structure in details.
Check out the [solution structure](../startup-templates/application#solution-structure) section to understand the structure in details.
### Create the book entity
@ -444,7 +444,7 @@ using Volo.Abp.Application.Services;
namespace Acme.BookStore
{
public interface IBookAppService :
public interface IBookAppService :
ICrudAppService< //Defines CRUD methods
BookDto, //Used to show books
Guid, //Primary key of the book entity
@ -473,12 +473,12 @@ using Volo.Abp.Domain.Repositories;
namespace Acme.BookStore
{
public class BookAppService :
public class BookAppService :
CrudAppService<Book, BookDto, Guid, PagedAndSortedResultRequestDto,
CreateUpdateBookDto, CreateUpdateBookDto>,
IBookAppService
{
public BookAppService(IRepository<Book, Guid> repository)
public BookAppService(IRepository<Book, Guid> repository)
: base(repository)
{
@ -564,17 +564,15 @@ Open the `Index.cshtml` and change the whole content as shown below:
````html
@page
@using Acme.BookStore.Web.Pages.Books
@inherits Acme.BookStore.Web.Pages.BookStorePage
@model IndexModel
<h2>Books</h2>
````
* This code changes the default inheritance of the Razor View Page Model so it **inherits** from the `BookStorePage` class (instead of `PageModel`). The `BookStorePage` class which comes with the startup template, provides some shared properties/methods used by all pages.
* Set the `IndexModel`'s namespace to `Acme.BookStore.Pages.Books` in `Index.cshtml.cs`.
**Index.cshtml.cs:**
@ -602,7 +600,7 @@ Open the `BookStoreMenuContributor` class in the `Menus` folder and add the foll
namespace Acme.BookStore.Web.Menus
{
public class BookStoreMenuContributor : IMenuContributor
{
{
private async Task ConfigureMainMenuAsync(MenuConfigurationContext context)
{
//<-- added the below code
@ -681,7 +679,6 @@ Change the `Pages/Books/Index.cshtml` as following:
````html
@page
@inherits Acme.BookStore.Web.Pages.BookStorePage
@model Acme.BookStore.Web.Pages.Books.IndexModel
@section scripts
{
@ -749,7 +746,7 @@ It's end of this part. The final UI of this work is shown as below:
{{end}}
{{if UI == "NG"}}
{{if UI == "NG"}}
### Angular development
#### Create the books page

22
docs/en/Tutorials/Part-2.md

@ -15,7 +15,7 @@ else if UI == "NG"
DB="mongodb"
DB_Text="MongoDB"
UI_Text="angular"
else
else
DB ="?"
UI_Text="?"
end
@ -86,7 +86,6 @@ Open the `CreateModal.cshtml` file and paste the code below:
````html
@page
@inherits Acme.BookStore.Web.Pages.BookStorePage
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@model Acme.BookStore.Web.Pages.Books.CreateModalModel
@{
@ -203,7 +202,7 @@ namespace Acme.BookStore.Web.Pages.Books
* In the `GetAsync` method, we get `BookDto `from `BookAppService` and this is being mapped to the DTO object `CreateUpdateBookDto`.
* The `OnPostAsync` uses `BookAppService.UpdateAsync()` to update the entity.
#### Mapping from BookDto to CreateUpdateBookDto
#### Mapping from BookDto to CreateUpdateBookDto
To be able to map the `BookDto` to `CreateUpdateBookDto`, configure a new mapping. To do this, open the `BookStoreWebAutoMapperProfile.cs` in the `Acme.BookStore.Web` project and change it as shown below:
@ -230,7 +229,6 @@ Replace `EditModal.cshtml` content with the following content:
````html
@page
@inherits Acme.BookStore.Web.Pages.BookStorePage
@using Acme.BookStore.Web.Pages.Books
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@model EditModalModel
@ -256,7 +254,7 @@ This page is very similar to the `CreateModal.cshtml`, except:
#### Add "Actions" dropdown to the table
We will add a dropdown button to the table named *Actions*.
We will add a dropdown button to the table named *Actions*.
Open the `Pages/Books/Index.cshtml` page and change the `<abp-table>` section as shown below:
@ -525,7 +523,7 @@ export class BookState {
* We imported `CreateUpdateBook` action and defined the `save` method that will listen to a `CreateUpdateBook` action to create a book.
When the `SaveBook` action dispatched, the save method is being executed. It calls `createByInput` method of the `BookService`.
When the `SaveBook` action dispatched, the save method is being executed. It calls `createByInput` method of the `BookService`.
#### Add a modal to BookListComponent
@ -601,7 +599,7 @@ Open `book-list.component.html` file in `books\book-list` folder and replace the
</abp-modal>
```
* We added the `abp-modal` which renders a modal to allow user to create a new book.
* We added the `abp-modal` which renders a modal to allow user to create a new book.
* `abp-modal` is a pre-built component to show modals. While you could use another approach to show a modal, `abp-modal` provides additional benefits.
* We added `New book` button to the `AbpContentToolbar`.
@ -864,7 +862,7 @@ export class BookListComponent implements OnInit {
}
```
* We imported ` NgbDateNativeAdapter, NgbDateAdapter`
* We imported ` NgbDateNativeAdapter, NgbDateAdapter`
* We added a new provider `NgbDateAdapter` that converts Datepicker value to `Date` type. See the [datepicker adapters](https://ng-bootstrap.github.io/#/components/datepicker/overview) for more details.
@ -971,7 +969,7 @@ Open `book-list.component.html` in `app\book\book-list` folder and add the follo
<button type="button" class="btn btn-secondary" #abpClose>
{%{{{ 'AbpAccount::Close' | abpLocalization }}}%}
</button>
<!--added save button-->
<button class="btn btn-primary" (click)="save()" [disabled]="form.invalid">
<i class="fa fa-check mr-1"></i>
@ -986,7 +984,7 @@ Find the `<form [formGroup]="form">` tag and replace below content:
<form [formGroup]="form" (ngSubmit)="save()"> <!-- added the ngSubmit -->
```
* We added the `(ngSubmit)="save()"` to `<form>` element to save a new book by pressing the enter.
* We added `abp-button` to the bottom area of the modal to save a new book.
@ -1131,7 +1129,7 @@ export class BookListComponent implements OnInit {
* We imported `BookService`.
* We declared a variable named `selectedBook` as `BookDto`.
* We injected `BookService` to the constructor. `BookService` is being used to retrieve the book data which is being edited.
* We added `editBook` method. This method fetches the book with the given `Id` and sets it to `selectedBook` object.
* We added `editBook` method. This method fetches the book with the given `Id` and sets it to `selectedBook` object.
* We replaced the `buildForm` method so that it creates the form with the `selectedBook` data.
* We replaced the `createBook` method so it sets `selectedBook` to an empty object.
* We added `selectedBook.id` to the constructor of the new `CreateUpdateBook`.
@ -1291,7 +1289,7 @@ import { ConfirmationService } from '@abp/ng.theme.shared';
//...
constructor(
private store: Store,
private store: Store,
private fb: FormBuilder,
private bookService: BookService,
private confirmation: ConfirmationService // <== added this line ==>

4
docs/en/UI/Angular/Component-Replacement.md

@ -543,6 +543,10 @@ The final UI looks like below:
![New nav-items](./images/replaced-nav-items-component.png)
## See Also
- [How to Replace PermissionManagementComponent](./Permission-Management-Component-Replacement.md)
## What's Next?
- [Custom Setting Page](./Custom-Setting-Page.md)

500
docs/en/UI/Angular/Permission-Management-Component-Replacement.md

@ -0,0 +1,500 @@
# How to Replace PermissionManagementComponent
![Permission management modal](./images/permission-management-modal.png)
Run the following command in `angular` folder to create a new component called `PermissionManagementComponent`.
```bash
yarn ng generate component permission-management --entryComponent --inlineStyle
# You don't need the --entryComponent option in Angular 9
```
Open the generated `permission-management.component.ts` in `src/app/permission-management` folder and replace the content with the following:
```js
import {
Component,
EventEmitter,
Input,
Output,
Renderer2,
TrackByFunction,
Inject,
Optional,
} from '@angular/core';
import { ReplaceableComponents } from '@abp/ng.core';
import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { finalize, map, pluck, take, tap } from 'rxjs/operators';
import {
GetPermissions,
UpdatePermissions,
PermissionManagement,
PermissionManagementState,
} from '@abp/ng.permission-management';
type PermissionWithMargin = PermissionManagement.Permission & {
margin: number;
};
@Component({
selector: 'app-permission-management',
templateUrl: './permission-management.component.html',
styles: [
`
.overflow-scroll {
max-height: 70vh;
overflow-y: scroll;
}
`,
],
})
export class PermissionManagementComponent
implements
PermissionManagement.PermissionManagementComponentInputs,
PermissionManagement.PermissionManagementComponentOutputs {
protected _providerName: string;
@Input()
get providerName(): string {
if (this.replaceableData) return this.replaceableData.inputs.providerName;
return this._providerName;
}
set providerName(value: string) {
this._providerName = value;
}
protected _providerKey: string;
@Input()
get providerKey(): string {
if (this.replaceableData) return this.replaceableData.inputs.providerKey;
return this._providerKey;
}
set providerKey(value: string) {
this._providerKey = value;
}
protected _hideBadges = false;
@Input()
get hideBadges(): boolean {
if (this.replaceableData) return this.replaceableData.inputs.hideBadges;
return this._hideBadges;
}
set hideBadges(value: boolean) {
this._hideBadges = value;
}
protected _visible = false;
@Input()
get visible(): boolean {
return this._visible;
}
set visible(value: boolean) {
if (value === this._visible) return;
if (value) {
this.openModal().subscribe(() => {
this._visible = true;
this.visibleChange.emit(true);
if (this.replaceableData) this.replaceableData.outputs.visibleChange(true);
});
} else {
this.selectedGroup = null;
this._visible = false;
this.visibleChange.emit(false);
if (this.replaceableData) this.replaceableData.outputs.visibleChange(false);
}
}
@Output() readonly visibleChange = new EventEmitter<boolean>();
@Select(PermissionManagementState.getPermissionGroups)
groups$: Observable<PermissionManagement.Group[]>;
@Select(PermissionManagementState.getEntityDisplayName)
entityName$: Observable<string>;
selectedGroup: PermissionManagement.Group;
permissions: PermissionManagement.Permission[] = [];
selectThisTab = false;
selectAllTab = false;
modalBusy = false;
trackByFn: TrackByFunction<PermissionManagement.Group> = (_, item) => item.name;
get selectedGroupPermissions$(): Observable<PermissionWithMargin[]> {
return this.groups$.pipe(
map((groups) =>
this.selectedGroup
? groups.find((group) => group.name === this.selectedGroup.name).permissions
: []
),
map<PermissionManagement.Permission[], PermissionWithMargin[]>((permissions) =>
permissions.map(
(permission) =>
(({
...permission,
margin: findMargin(permissions, permission),
isGranted: this.permissions.find((per) => per.name === permission.name).isGranted,
} as any) as PermissionWithMargin)
)
)
);
}
get isVisible(): boolean {
if (!this.replaceableData) return this.visible;
return this.replaceableData.inputs.visible;
}
constructor(
@Optional()
@Inject('REPLACEABLE_DATA')
public replaceableData: ReplaceableComponents.ReplaceableTemplateData<
PermissionManagement.PermissionManagementComponentInputs,
PermissionManagement.PermissionManagementComponentOutputs
>,
private store: Store
) {}
getChecked(name: string) {
return (this.permissions.find((per) => per.name === name) || { isGranted: false }).isGranted;
}
isGrantedByOtherProviderName(grantedProviders: PermissionManagement.GrantedProvider[]): boolean {
if (grantedProviders.length) {
return grantedProviders.findIndex((p) => p.providerName !== this.providerName) > -1;
}
return false;
}
onClickCheckbox(clickedPermission: PermissionManagement.Permission, value) {
if (
clickedPermission.isGranted &&
this.isGrantedByOtherProviderName(clickedPermission.grantedProviders)
)
return;
setTimeout(() => {
this.permissions = this.permissions.map((per) => {
if (clickedPermission.name === per.name) {
return { ...per, isGranted: !per.isGranted };
} else if (clickedPermission.name === per.parentName && clickedPermission.isGranted) {
return { ...per, isGranted: false };
} else if (clickedPermission.parentName === per.name && !clickedPermission.isGranted) {
return { ...per, isGranted: true };
}
return per;
});
this.setTabCheckboxState();
this.setGrantCheckboxState();
}, 0);
}
setTabCheckboxState() {
this.selectedGroupPermissions$.pipe(take(1)).subscribe((permissions) => {
const selectedPermissions = permissions.filter((per) => per.isGranted);
const element = document.querySelector('#select-all-in-this-tabs') as any;
if (selectedPermissions.length === permissions.length) {
element.indeterminate = false;
this.selectThisTab = true;
} else if (selectedPermissions.length === 0) {
element.indeterminate = false;
this.selectThisTab = false;
} else {
element.indeterminate = true;
}
});
}
setGrantCheckboxState() {
const selectedAllPermissions = this.permissions.filter((per) => per.isGranted);
const checkboxElement = document.querySelector('#select-all-in-all-tabs') as any;
if (selectedAllPermissions.length === this.permissions.length) {
checkboxElement.indeterminate = false;
this.selectAllTab = true;
} else if (selectedAllPermissions.length === 0) {
checkboxElement.indeterminate = false;
this.selectAllTab = false;
} else {
checkboxElement.indeterminate = true;
}
}
onClickSelectThisTab() {
this.selectedGroupPermissions$.pipe(take(1)).subscribe((permissions) => {
permissions.forEach((permission) => {
if (permission.isGranted && this.isGrantedByOtherProviderName(permission.grantedProviders))
return;
const index = this.permissions.findIndex((per) => per.name === permission.name);
this.permissions = [
...this.permissions.slice(0, index),
{ ...this.permissions[index], isGranted: !this.selectThisTab },
...this.permissions.slice(index + 1),
];
});
});
this.setGrantCheckboxState();
}
onClickSelectAll() {
this.permissions = this.permissions.map((permission) => ({
...permission,
isGranted:
this.isGrantedByOtherProviderName(permission.grantedProviders) || !this.selectAllTab,
}));
this.selectThisTab = !this.selectAllTab;
}
onChangeGroup(group: PermissionManagement.Group) {
this.selectedGroup = group;
this.setTabCheckboxState();
}
submit() {
this.modalBusy = true;
const unchangedPermissions = getPermissions(
this.store.selectSnapshot(PermissionManagementState.getPermissionGroups)
);
const changedPermissions: PermissionManagement.MinimumPermission[] = this.permissions
.filter((per) =>
unchangedPermissions.find((unchanged) => unchanged.name === per.name).isGranted ===
per.isGranted
? false
: true
)
.map(({ name, isGranted }) => ({ name, isGranted }));
if (changedPermissions.length) {
this.store
.dispatch(
new UpdatePermissions({
providerKey: this.providerKey,
providerName: this.providerName,
permissions: changedPermissions,
})
)
.pipe(finalize(() => (this.modalBusy = false)))
.subscribe(() => {
this.visible = false;
});
} else {
this.modalBusy = false;
this.visible = false;
}
}
openModal() {
if (!this.providerKey || !this.providerName) {
throw new Error('Provider Key and Provider Name are required.');
}
return this.store
.dispatch(
new GetPermissions({
providerKey: this.providerKey,
providerName: this.providerName,
})
)
.pipe(
pluck('PermissionManagementState', 'permissionRes'),
tap((permissionRes: PermissionManagement.Response) => {
this.selectedGroup = permissionRes.groups[0];
this.permissions = getPermissions(permissionRes.groups);
})
);
}
initModal() {
this.setTabCheckboxState();
this.setGrantCheckboxState();
}
onVisibleChange(visible: boolean) {
this.visible = visible;
if (this.replaceableData) {
this.replaceableData.inputs.visible = visible;
this.replaceableData.outputs.visibleChange(visible);
}
}
}
function findMargin(
permissions: PermissionManagement.Permission[],
permission: PermissionManagement.Permission
) {
const parentPermission = permissions.find((per) => per.name === permission.parentName);
if (parentPermission && parentPermission.parentName) {
let margin = 20;
return (margin += findMargin(permissions, parentPermission));
}
return parentPermission ? 20 : 0;
}
function getPermissions(groups: PermissionManagement.Group[]): PermissionManagement.Permission[] {
return groups.reduce((acc, val) => [...acc, ...val.permissions], []);
}
```
Open the generated `permission-management.component.html` in `src/app/permission-management` folder and replace the content with the below:
```html
<abp-modal
[visible]="isVisible"
(visibleChange)="onVisibleChange($event)"
(init)="initModal()"
[busy]="modalBusy"
>
<ng-container *ngIf="{ entityName: entityName$ | async } as data">
<ng-template #abpHeader>
<h4>
{%{{{ 'AbpPermissionManagement::Permissions' | abpLocalization }}}%} - {%{{{ data.entityName }}}%}
</h4>
</ng-template>
<ng-template #abpBody>
<div class="custom-checkbox custom-control mb-2">
<input
type="checkbox"
id="select-all-in-all-tabs"
name="select-all-in-all-tabs"
class="custom-control-input"
[(ngModel)]="selectAllTab"
(click)="onClickSelectAll()"
/>
<label class="custom-control-label" for="select-all-in-all-tabs">{%{{{
'AbpPermissionManagement::SelectAllInAllTabs' | abpLocalization
}}}%}</label>
</div>
<hr class="mt-2 mb-2" />
<div class="row">
<div class="overflow-scroll col-md-4">
<ul class="nav nav-pills flex-column">
<li *ngFor="let group of groups$ | async; trackBy: trackByFn" class="nav-item">
<a
class="nav-link pointer"
[class.active]="selectedGroup?.name === group?.name"
(click)="onChangeGroup(group)"
>{%{{{ group?.displayName }}}%}</a
>
</li>
</ul>
</div>
<div class="col-md-8 overflow-scroll">
<h4>{%{{{ selectedGroup?.displayName }}}%}</h4>
<hr class="mt-2 mb-3" />
<div class="pl-1 pt-1">
<div class="custom-checkbox custom-control mb-2">
<input
type="checkbox"
id="select-all-in-this-tabs"
name="select-all-in-this-tabs"
class="custom-control-input"
[(ngModel)]="selectThisTab"
(click)="onClickSelectThisTab()"
/>
<label class="custom-control-label" for="select-all-in-this-tabs">{%{{{
'AbpPermissionManagement::SelectAllInThisTab' | abpLocalization
}}}%}</label>
</div>
<hr class="mb-3" />
<div
*ngFor="
let permission of selectedGroupPermissions$ | async;
let i = index;
trackBy: trackByFn
"
[style.margin-left]="permission.margin + 'px'"
class="custom-checkbox custom-control mb-2"
>
<input
#permissionCheckbox
type="checkbox"
[checked]="getChecked(permission.name)"
[value]="getChecked(permission.name)"
[attr.id]="permission.name"
class="custom-control-input"
[disabled]="isGrantedByOtherProviderName(permission.grantedProviders)"
/>
<label
class="custom-control-label"
[attr.for]="permission.name"
(click)="onClickCheckbox(permission, permissionCheckbox.value)"
>{%{{{ permission.displayName }}}%}
<ng-container *ngIf="!hideBadges">
<span
*ngFor="let provider of permission.grantedProviders"
class="badge badge-light"
>{%{{{ provider.providerName }}}%}: {%{{{ provider.providerKey }}}%}</span
>
</ng-container>
</label>
</div>
</div>
</div>
</div>
</ng-template>
<ng-template #abpFooter>
<button type="button" class="btn btn-secondary" #abpClose>
{%{{{ 'AbpIdentity::Cancel' | abpLocalization }}}%}
</button>
<abp-button iconClass="fa fa-check" (click)="submit()">{%{{{
'AbpIdentity::Save' | abpLocalization
}}}%}</abp-button>
</ng-template>
</ng-container>
</abp-modal>
```
Open `app.component.ts` in `src/app` folder and modify it as shown below:
```js
import { AddReplaceableComponent } from '@abp/ng.core';
import { ePermissionManagementComponents } from '@abp/ng.permission-management';
import { Component, OnInit } from '@angular/core';
import { Store } from '@ngxs/store';
import { PermissionManagementComponent } from './permission-management/permission-management.component';
//...
export class AppComponent implements OnInit {
constructor(private store: Store) {} // injected store
ngOnInit() {
// added dispatching the AddReplaceableComponent action
this.store.dispatch(
new AddReplaceableComponent({
component: PermissionManagementComponent,
key: ePermissionManagementComponents.PermissionManagement,
})
);
}
}
```
## See Also
- [Component Replacement](./Component-Replacement.md)

BIN
docs/en/UI/Angular/images/permission-management-modal.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 KiB

6
docs/en/UI/AspNetCore/Customization-User-Interface.md

@ -51,15 +51,13 @@ namespace Acme.BookStore.Web.Pages.Identity.Users
### Overriding a Razor Page (.CSHTML)
Overriding a `.cshtml` file (razor page, razor view, view component... etc.) is possible through the [Virtual File System](../../Virtual-File-System.md).
Virtual File system allows us to **embed resources into assemblies**. In this way, pre-built modules define the razor pages inside their NuGet packages. When you depend a module, you can override any file added to the virtual file system by that module, including pages/views.
Overriding a `.cshtml` file (razor page, razor view, view component... etc.) is possible through creating the same `.cshtml` file under the same path.
#### Example
This example overrides the **login page** UI defined by the [Account Module](../../Modules/Account.md).
Physical files override the embedded files defined in the same location. The account module defines a `Login.cshtml` file under the `Pages/Account` folder. So, you can override it by creating a file in the same path:
The account module defines a `Login.cshtml` file under the `Pages/Account` folder. So, you can override it by creating a file in the same path:
![overriding-login-cshtml](../../images/overriding-login-cshtml.png)

40
docs/en/UI/AspNetCore/Tag-Helpers/Badges.md

@ -0,0 +1,40 @@
# Badges
## Introduction
`abp-badge` and `abp-badge-pill` are abp tags for badges.
Basic usage:
````csharp
<span abp-badge="Primary">Primary</span>
<a abp-badge="Info" href="#">Info</a>
<a abp-badge-pill="Danger" href="#">Danger</a>
````
## Demo
See the [badges demo page](https://bootstrap-taghelpers.abp.io/Components/Badges) to see it in action.
### Values
* Indicates the type of the badge. Should be one of the following values:
* `_` (default value)
* `Default` (default value)
* `Primary`
* `Secondary`
* `Success`
* `Danger`
* `Warning`
* `Info`
* `Light`
* `Dark`
Example:
````csharp
<span abp-badge-pill="Danger">Danger</span>
````

126
docs/en/UI/AspNetCore/Tag-Helpers/Borders.md

@ -0,0 +1,126 @@
# Borders
## Introduction
`abp-border` is a main element for border styling.
Basic usage:
````csharp
<span abp-border="Default"></span>
<span abp-border="Top"></span>
<span abp-border="Right"></span>
<span abp-border="Bottom"></span>
<span abp-border="Left"></span>
````
## Demo
See the [borders demo page](https://bootstrap-taghelpers.abp.io/Components/Borders) to see it in action.
## Values
A value indicates type, position and the color of the border. Should be one of the following values:
* `Default`
* `_0`
* `Primary`
* `Secondary`
* `Success`
* `Danger`
* `Warning`
* `Info`
* `Light`
* `Dark`
* `White`
* `Primary_0`
* `Secondary_0`
* `Success_0`
* `Danger_0`
* `Warning_0`
* `Info_0`
* `Light_0`
* `Dark_0`
* `White_0`
* `Top`
* `Top_0`
* `Top_Primary`
* `Top_Secondary`
* `Top_Success`
* `Top_Danger`
* `Top_Warning`
* `Top_Info`
* `Top_Light`
* `Top_Dark`
* `Top_White`
* `Top_Primary_0`
* `Top_Secondary_0`
* `Top_Success_0`
* `Top_Danger_0`
* `Top_Warning_0`
* `Top_Info_0`
* `Top_Light_0`
* `Top_Dark_0`
* `Top_White_0`
* `Right`
* `Right_0`
* `Right_Primary`
* `Right_Secondary`
* `Right_Success`
* `Right_Danger`
* `Right_Warning`
* `Right_Info`
* `Right_Light`
* `Right_Dark`
* `Right_White`
* `Right_Primary_0`
* `Right_Secondary_0`
* `Right_Success_0`
* `Right_Danger_0`
* `Right_Warning_0`
* `Right_Info_0`
* `Right_Light_0`
* `Right_Dark_0`
* `Right_White_0`
* `Left`
* `Left_0`
* `Left_Primary`
* `Left_Secondary`
* `Left_Success`
* `Left_Danger`
* `Left_Warning`
* `Left_Info`
* `Left_Light`
* `Left_Dark`
* `Left_White`
* `Left_Primary_0`
* `Left_Secondary_0`
* `Left_Success_0`
* `Left_Danger_0`
* `Left_Warning_0`
* `Left_Info_0`
* `Left_Light_0`
* `Left_Dark_0`
* `Left_White_0`
* `Bottom`
* `Bottom_0`
* `Bottom_Primary`
* `Bottom_Secondary`
* `Bottom_Success`
* `Bottom_Danger`
* `Bottom_Warning`
* `Bottom_Info`
* `Bottom_Light`
* `Bottom_Dark`
* `Bottom_White`
* `Bottom_Primary_0`
* `Bottom_Secondary_0`
* `Bottom_Success_0`
* `Bottom_Danger_0`
* `Bottom_Warning_0`
* `Bottom_Info_0`
* `Bottom_Light_0`
* `Bottom_Dark_0`
* `Bottom_White_0`

25
docs/en/UI/AspNetCore/Tag-Helpers/Breadcrumbs.md

@ -0,0 +1,25 @@
# Breadcrumbs
## Introduction
`abp-breadcrumb` is the main container for breadcrumb items.
Basic usage:
````csharp
<abp-breadcrumb>
<abp-breadcrumb-item href="#" title="Home" />
<abp-breadcrumb-item href="#" title="Library"/>
<abp-breadcrumb-item title="Page"/>
</abp-breadcrumb>
````
## Demo
See the [breadcrumbs demo page](https://bootstrap-taghelpers.abp.io/Components/Breadcrumbs) to see it in action.
## abp-breadcrumb-item Attributes
- **title**: Sets the text of the breadcrumb item.
- **active**: Sets the active breadcrumb item. Last item is active by default, if no other item is active.
- **href**: A value indicates if an `abp-breadcrumb-item` has a link. Should be a string link value.

114
docs/en/UI/AspNetCore/Tag-Helpers/Navs.md

@ -0,0 +1,114 @@
# Navs
## Introduction
`abp-nav` is the basic tag helper component derived from bootstrap nav element.
Basic usage:
````csharp
<abp-nav nav-style="Pill" align="Center">
<abp-nav-item>
<a abp-nav-link active="true" href="#">Active</a>
</abp-nav-item>
<abp-nav-item>
<a abp-nav-link href="#">Longer nav link</a>
</abp-nav-item>
<abp-nav-item>
<a abp-nav-link href="#">link</a>
</abp-nav-item>
<abp-nav-item>
<a abp-nav-link disabled="true" href="#">disabled</a>
</abp-nav-item>
</abp-nav>
````
## Demo
See the [navs demo page](https://bootstrap-taghelpers.abp.io/Components/Navs) to see it in action.
## abp-nav Attributes
- **nav-style**: The value indicates the positioning and style of the containing items. Should be one of the following values:
* `Default` (default value)
* `Vertical`
* `Pill`
* `PillVertical`
- **align:** The value indicates the alignment of the containing items:
* `Default` (default value)
* `Start`
* `Center`
* `End`
### abp-nav-bar Attributes
- **nav-style**: The value indicates the color layout of the base navigation bar. Should be one of the following values:
* `Default` (default value)
* `Dark`
* `Light`
* `Dark_Primary`
* `Dark_Secondary`
* `Dark_Success`
* `Dark_Danger`
* `Dark_Warning`
* `Dark_Info`
* `Dark_Dark`
* `Dark_Link`
* `Light_Primary`
* `Light_Secondary`
* `Light_Success`
* `Light_Danger`
* `Light_Warning`
* `Light_Info`
* `Light_Dark`
* `Light_Link`
- **size:** The value indicates size of the base navigation bar. Should be one of the following values:
* `Default` (default value)
* `Sm`
* `Md`
* `Lg`
* `Xl`
### abp-nav-item Attributes
**dropdown**: A value that sets the navigation item to be a dropdown menu if provided. Can be one of the following values:
* `false` (default value)
* `true`
Example:
````csharp
<abp-nav-bar size="Lg" navbar-style="Dark_Warning">
<a abp-navbar-brand href="#">Navbar</a>
<abp-navbar-toggle>
<abp-navbar-nav>
<abp-nav-item active="true">
<a abp-nav-link href="#">Home <span class="sr-only">(current)</span></a>
</abp-nav-item>
<abp-nav-item>
<a abp-nav-link href="#">Link</a>
</abp-nav-item>
<abp-nav-item dropdown="true">
<abp-dropdown>
<abp-dropdown-button nav-link="true" text="Dropdown" />
<abp-dropdown-menu>
<abp-dropdown-header>Dropdown header</abp-dropdown-header>
<abp-dropdown-item href="#" active="true">Action</abp-dropdown-item>
<abp-dropdown-item href="#" disabled="true">Another disabled action</abp-dropdown-item>
<abp-dropdown-item href="#">Something else here</abp-dropdown-item>
<abp-dropdown-divider />
<abp-dropdown-item href="#">Separated link</abp-dropdown-item>
</abp-dropdown-menu>
</abp-dropdown>
</abp-nav-item>
<abp-nav-item>
<a abp-nav-link disabled="true" href="#">Disabled</a>
</abp-nav-item>
</abp-navbar-nav>
<span abp-navbar-text>
Sample Text
</span>
</abp-navbar-toggle>
</abp-nav-bar>
````

61
docs/en/UI/AspNetCore/Tag-Helpers/Tables.md

@ -0,0 +1,61 @@
# Tables
## Introduction
`abp-table` is the basic tag component for tables in abp.
Basic usage:
````csharp
<abp-table hoverable-rows="true" responsive-sm="true">
<thead>
<tr>
<th scope="Column">#</th>
<th scope="Column">First</th>
<th scope="Column">Last</th>
<th scope="Column">Handle</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="Row">1</th>
<td>Mark</td>
<td>Otto</td>
<td table-style="Danger">mdo</td>
</tr>
<tr table-style="Warning">
<th scope="Row">2</th>
<td>Jacob</td>
<td>Thornton</td>
<td>fat</td>
</tr>
<tr>
<th scope="Row">3</th>
<td table-style="Success">Larry</td>
<td>the Bird</td>
<td>twitter</td>
</tr>
</tbody>
</abp-table>
````
## Demo
See the [tables demo page](https://bootstrap-taghelpers.abp.io/Components/Tables) to see it in action.
## abp-table Attributes
- **responsive**: Used to create responsive tables up to a particular breakpoint. see [breakpoint specific](https://getbootstrap.com/docs/4.1/content/tables/#breakpoint-specific) for more information.
- **responsive-sm**: If not set to false, sets the table responsiveness for small screen devices.
- **responsive-md**: If not set to false, sets the table responsiveness for medium screen devices.
- **responsive-lg**: If not set to false, sets the table responsiveness for large screen devices.
- **responsive-xl**: If not set to false, sets the table responsiveness for extra large screen devices.
- **dark-theme**: If set to true, sets the table color theme to dark.
- **striped-rows**: If set to true, adds zebra-striping to table rows.
- **hoverable-rows**: If set to true, adds hover state to table rows.
- **border-style**: Sets the border style of the table. Should be one of the following values:
- `Default` (default)
- `Bordered`
- `Borderless`

10
docs/en/Virtual-File-System.md

@ -1,6 +1,6 @@
## Virtual File System
The Virtual File System makes it possible to manage files that do not physically exist on the file system (disk). It's mainly used to embed (js, css, image, cshtml...) files into assemblies and use them like physical files at runtime.
The Virtual File System makes it possible to manage files that do not physically exist on the file system (disk). It's mainly used to embed (js, css, image..) files into assemblies and use them like physical files at runtime.
### Volo.Abp.VirtualFileSystem Package
@ -147,7 +147,7 @@ The code above assumes that `MyWebAppModule` and `MyModule` are two different pr
The Virtual File System is well integrated to ASP.NET Core:
* Virtual files can be used just like physical (static) files in a web application.
* Razor Views, Razor Pages, js, css, image files and all other web content types can be embedded into assemblies and used just like the physical files.
* Js, css, image files and all other web content types can be embedded into assemblies and used just like the physical files.
* An application (or another module) can override a virtual file of a module just like placing a file with the same name and extension into the same folder of the virtual file.
#### Virtual Files Middleware
@ -161,9 +161,3 @@ app.UseVirtualFiles();
Adding virtual files middleware after the static files middleware makes it possible to override a virtual file with a real physical file simply by placing it in the same location as the virtual file.
>The Virtual File Middleware only serves the virtual wwwroot folder contents - just like the other static files.
#### Views & Pages
Embedded razor views/pages are available in the application without any configuration. Simply place them into the standard Views/Pages virtual folders of the module being developed.
An embedded view/page can be overrided if a module/application locates a new file into the same location as mentioned above.

38
docs/en/docs-nav.json

@ -179,6 +179,36 @@
"text": "Object to object mapping",
"path": "Object-To-Object-Mapping.md"
},
{
"text": "BLOB Storing",
"items": [
{
"text": "BLOB Storing System",
"path": "Blob-Storing.md"
},
{
"text": "Storage Providers",
"items": [
{
"text": "File System Provider",
"path": "Blob-Storing-File-System.md"
},
{
"text": "Database Provider",
"path": "Blob-Storing-Database.md"
},
{
"text": "Azure Provider",
"path": "Blob-Storing-Azure.md"
},
{
"text": "Create a Custom Provider",
"path": "Blob-Storing-Custom-Provider.md"
}
]
}
]
},
{
"text": "Text Templating",
"path": "Text-Templating.md"
@ -520,6 +550,10 @@
{
"text": "Module",
"path": "Startup-Templates/Module.md"
},
{
"text": "Console",
"path": "Startup-Templates/Console.md"
}
]
},
@ -555,6 +589,10 @@
{
"text": "Contribution Guide",
"path": "Contribution/Index.md"
},
{
"text": "API Documentation",
"path": "{ApiDocumentationUrl}"
}
]
}

11
docs/pt-BR/Tutorials/AspNetCore-Mvc/Part-I.md

@ -222,7 +222,7 @@ using Volo.Abp.Application.Services;
namespace Acme.BookStore
{
public interface IBookAppService :
public interface IBookAppService :
ICrudAppService< //Defines CRUD methods
BookDto, //Used to show books
Guid, //Primary key of the book entity
@ -251,12 +251,12 @@ using Volo.Abp.Domain.Repositories;
namespace Acme.BookStore
{
public class BookAppService :
public class BookAppService :
CrudAppService<Book, BookDto, Guid, PagedAndSortedResultRequestDto,
CreateUpdateBookDto, CreateUpdateBookDto>,
IBookAppService
{
public BookAppService(IRepository<Book, Guid> repository)
public BookAppService(IRepository<Book, Guid> repository)
: base(repository)
{
@ -338,13 +338,11 @@ Abra `Index.cshtml`e altere o conteúdo, como mostrado abaixo:
```html
@page
@using Acme.BookStore.Web.Pages.Books
@inherits Acme.BookStore.Web.Pages.BookStorePage
@model IndexModel
<h2>Books</h2>
```
- Esse código altera a herança padrão do Razor View Page Model para que ele **herda** da `BookStorePage`classe (em vez de `PageModel`). A `BookStorePage`classe que acompanha o modelo de inicialização e fornece algumas propriedades / métodos compartilhados usados por todas as páginas.
- Verifique se o `IndexModel`( *Index.cshtml.cs)* possui o `Acme.BookStore.Pages.Books`espaço para nome ou atualize-o no `Index.cshtml`.
#### Adicionar página de livros ao menu principal
@ -395,7 +393,6 @@ Altere o `Pages/Books/Index.cshtml`seguinte:
```html
@page
@inherits Acme.BookStore.Web.Pages.BookStorePage
@model Acme.BookStore.Web.Pages.Books.IndexModel
@section scripts
{
@ -459,4 +456,4 @@ A interface do usuário final é mostrada abaixo:
### Próxima parte
Veja a [próxima parte](https://docs.abp.io/en/abp/latest/Tutorials/AspNetCore-Mvc/Part-II) deste tutorial.
Veja a [próxima parte](https://docs.abp.io/en/abp/latest/Tutorials/AspNetCore-Mvc/Part-II) deste tutorial.

8
docs/pt-BR/Tutorials/AspNetCore-Mvc/Part-II.md

@ -67,7 +67,6 @@ Abra o `CreateModal.cshtml`arquivo e cole o código abaixo:
```html
@page
@inherits Acme.BookStore.Web.Pages.BookStorePage
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@model Acme.BookStore.Web.Pages.Books.CreateModalModel
@{
@ -86,13 +85,13 @@ Abra o `CreateModal.cshtml`arquivo e cole o código abaixo:
- Este modal usa o
- Este modal usa o
```
abp-dynamic-form
```
auxiliar de marca para criar automaticamente o formulário a partir da
auxiliar de marca para criar automaticamente o formulário a partir da
```
CreateBookViewModel
@ -234,7 +233,6 @@ Substitua o `EditModal.cshtml`conteúdo pelo seguinte:
```html
@page
@inherits Acme.BookStore.Web.Pages.BookStorePage
@using Acme.BookStore.Web.Pages.Books
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@model EditModalModel
@ -464,4 +462,4 @@ Execute o aplicativo e tente excluir um livro.
### Próxima parte
Veja a [próxima parte](https://docs.abp.io/en/abp/latest/Tutorials/AspNetCore-Mvc/Part-III) deste tutorial.
Veja a [próxima parte](https://docs.abp.io/en/abp/latest/Tutorials/AspNetCore-Mvc/Part-III) deste tutorial.

5
docs/zh-Hans/How-To/Customize-Login-Page-MVC.md

@ -26,7 +26,7 @@ public class CustomLoginModel : LoginModel
## 重写登录页面UI
**Pages** 目录下创建名为 **Account** 的文件夹,并在这个文件夹中创建 `Login.cshtml` ,借助[虚拟文件系统](../Virtual-File-System.md)它会自动覆盖账户模块的页面文件.
**Pages** 目录下创建名为 **Account** 的文件夹,并在这个文件夹中创建 `Login.cshtml` , 它会自动覆盖账户模块的页面文件.
自定义页面一个很好的开始是复制它的源代码. [点击这里](https://github.com/abpframework/abp/blob/dev/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml)找到登录页面的源码. 在编写本文档时,源代码如下:
@ -35,7 +35,6 @@ public class CustomLoginModel : LoginModel
@using Volo.Abp.Account.Settings
@using Volo.Abp.Settings
@model Acme.BookStore.Web.Pages.Account.CustomLoginModel
@inherits Volo.Abp.Account.Web.Pages.Account.AccountPage
@inject Volo.Abp.Settings.ISettingProvider SettingProvider
@if (Model.EnableLocalLogin)
{
@ -110,4 +109,4 @@ public class CustomLoginModel : LoginModel
## 另请参阅
* [ASP.NET Core (MVC / Razor Pages) 用户界面自定义指南](../UI/AspNetCore/Customization-User-Interface.md).
* [ASP.NET Core (MVC / Razor Pages) 用户界面自定义指南](../UI/AspNetCore/Customization-User-Interface.md).

15
docs/zh-Hans/Tutorials/Part-1.md

@ -14,7 +14,7 @@ else if UI == "NG"
DB="mongodb"
DB_Text="MongoDB"
UI_Text="angular"
else
else
DB ="?"
UI_Text="?"
end
@ -444,7 +444,7 @@ using Volo.Abp.Application.Services;
namespace Acme.BookStore
{
public interface IBookAppService :
public interface IBookAppService :
ICrudAppService< //定义了CRUD方法
BookDto, //用来展示书籍
Guid, //Book实体的主键
@ -473,12 +473,12 @@ using Volo.Abp.Domain.Repositories;
namespace Acme.BookStore
{
public class BookAppService :
public class BookAppService :
CrudAppService<Book, BookDto, Guid, PagedAndSortedResultRequestDto,
CreateUpdateBookDto, CreateUpdateBookDto>,
IBookAppService
{
public BookAppService(IRepository<Book, Guid> repository)
public BookAppService(IRepository<Book, Guid> repository)
: base(repository)
{
@ -564,13 +564,11 @@ successfully created the book with id: 439b0ea8-923e-8e1e-5d97-39f2c7ac4246
````html
@page
@using Acme.BookStore.Web.Pages.Book
@inherits Acme.BookStore.Web.Pages.BookStorePage
@model IndexModel
<h2>Book</h2>
````
* 此代码更改了Razor View Page Model的默认继承,因此它从`BookStorePage`类(而不是`PageModel`)继承.启动模板附带的`BookStorePage`类,提供所有页面使用的一些共享属性/方法.
* 确保`IndexModel`(Index.cshtml.cs)具有`Acme.BookStore.Web.Pages.Book`命名空间,或者在`Index.cshtml`中更新它.
**Index.cshtml.cs:**
@ -599,7 +597,7 @@ namespace Acme.BookStore.Web.Pages.Book
namespace Acme.BookStore.Web.Menus
{
public class BookStoreMenuContributor : IMenuContributor
{
{
private async Task ConfigureMainMenuAsync(MenuConfigurationContext context)
{
//<-- added the below code
@ -667,7 +665,6 @@ namespace Acme.BookStore.Web.Menus
````html
@page
@inherits Acme.BookStore.Web.Pages.BookStorePage
@model Acme.BookStore.Web.Pages.Book.IndexModel
@section scripts
{
@ -1060,4 +1057,4 @@ export class BookListComponent implements OnInit {
### 下一章
参阅[第二章](part-2.md)了解创建,更新和删除图书.
参阅[第二章](part-2.md)了解创建,更新和删除图书.

10
docs/zh-Hans/Tutorials/Part-2.md

@ -15,7 +15,7 @@ else if UI == "NG"
DB="mongodb"
DB_Text="MongoDB"
UI_Text="angular"
else
else
DB ="?"
UI_Text="?"
end
@ -86,7 +86,6 @@ namespace Acme.BookStore.Web.Pages.Books
````html
@page
@inherits Acme.BookStore.Web.Pages.BookStorePage
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@model Acme.BookStore.Web.Pages.Books.CreateModalModel
@{
@ -230,7 +229,6 @@ namespace Acme.BookStore.Web
````html
@page
@inherits Acme.BookStore.Web.Pages.BookStorePage
@using Acme.BookStore.Web.Pages.Books
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@model EditModalModel
@ -866,7 +864,7 @@ export class BookListComponent implements OnInit {
}
```
* 我们导入了 ` NgbDateNativeAdapter, NgbDateAdapter`
* 我们导入了 ` NgbDateNativeAdapter, NgbDateAdapter`
* 我们添加了一个新的 `NgbDateAdapter` 提供程序,它将Datepicker值转换为Date类型. 有关更多详细信息,请参见[datepicker adapters](https://ng-bootstrap.github.io/#/components/datepicker/overview).
@ -972,7 +970,7 @@ export class BookListComponent implements OnInit {
<button type="button" class="btn btn-secondary" #abpClose>
{%{{{ 'AbpAccount::Close' | abpLocalization }}}%}
</button>
<!--added save button-->
<button class="btn btn-primary" (click)="save()" [disabled]="form.invalid">
<i class="fa fa-check mr-1"></i>
@ -1290,7 +1288,7 @@ import { ConfirmationService } from '@abp/ng.theme.shared';
//...
constructor(
private store: Store,
private store: Store,
private fb: FormBuilder,
private bookService: BookService,
private confirmation: ConfirmationService // <== added this line ==>

6
docs/zh-Hans/UI/AspNetCore/Customization-User-Interface.md

@ -51,15 +51,13 @@ namespace Acme.BookStore.Web.Pages.Identity.Users
### 重写Razor页面 (.CSHTML)
使用[虚拟文件系统](../../Virtual-File-System.md)可以重写 `.cshtml` 文件(razor page, razor view, view component... 等.)
虚拟文件系统允许我们将**资源嵌入到程序集中**. 通过这个方式,预构建的模块在Nuget包中定义了Razor页面. 当你依赖模块时,可以覆盖这个模块向虚拟文件系统添加的任何文件,包括页面/视图.
同一路径下创建相同的`.cshtml`文件可以实现重写功能(razor page, razor view, view component... 等.)
#### 示例
这个示例重写了[账户模块](../../Modules/Account.md)定义的**登录页面**UI
物理文件可以覆盖相同位置的嵌入文件. 账户模块在 `Pages/Account` 文件夹下定义了 `Login.cshtml` 文件. 所以你可以在同一路径下创建文件覆盖它:
账户模块在 `Pages/Account` 文件夹下定义了 `Login.cshtml` 文件. 所以你可以在同一路径下创建文件覆盖它:
![overriding-login-cshtml](../../images/overriding-login-cshtml.png)
通常你想要拷贝模块的 `.cshtml` 原文件,然后进行需要的更改. 你可以在[这里](https://github.com/abpframework/abp/blob/dev/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml)找到源文件. 不要拷贝 `Login.cshtml.cs` 文件,它是隐藏razor页面的代码,我们不希望覆盖它(见下节).

10
docs/zh-Hans/Virtual-File-System.md

@ -1,6 +1,6 @@
## 虚拟文件系统
虚拟文件系统使得管理物理上不存在于文件系统中(磁盘)的文件成为可能. 它主要用于将(js, css, image, cshtml ...)文件嵌入到程序集中, 并在运行时将它们象物理文件一样使用.
虚拟文件系统使得管理物理上不存在于文件系统中(磁盘)的文件成为可能. 它主要用于将(js, css, image...)文件嵌入到程序集中, 并在运行时将它们象物理文件一样使用.
### Volo.Abp.VirtualFileSystem nuget包
@ -147,7 +147,7 @@ public class MyWebAppModule : AbpModule
虚拟文件系统与 ASP.NET Core 无缝集成:
* 虚拟文件可以像Web应用程序上的物理(静态)文件一样使用.
* Razor Views, Razor Pages, js, css, 图像文件和所有其他Web内容可以嵌入到程序集中并像物理文件一样使用.
* Js, css, 图像文件和所有其他Web内容可以嵌入到程序集中并像物理文件一样使用.
* 应用程序(或其他模块)可以覆盖模块的虚拟文件, 就像将具有相同名称和扩展名的文件放入虚拟文件的同一文件夹中一样.
#### 虚拟文件中间件
@ -161,9 +161,3 @@ app.UseVirtualFiles();
在静态文件中间件之后添加虚拟文件中间件, 使得通过在虚拟文件相同的位置放置物理文件, 从而用物理文件覆盖虚拟文件成为可能.
> 虚拟文件中间件可以虚拟wwwroot文件夹中的内容 - 就像静态文件一样.
#### Views & Pages
无需任何配置即可在应用程序中使用嵌入式的 razor Views/pages. 只需要将它们放置在要开发的模块中的标准 Views/Pages 虚拟文件夹即可.
如果模块/应用程序将新文件放置同一位置, 则会覆盖嵌入式的 Views/Pages.

30
framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/RazorPages/AbpPageModel.cs

@ -28,7 +28,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.RazorPages
protected TService LazyGetRequiredService<TService>(ref TService reference)
=> LazyGetRequiredService(typeof(TService), ref reference);
protected TRef LazyGetRequiredService<TRef>(Type serviceType, ref TRef reference)
{
if (reference == null)
@ -45,16 +45,16 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.RazorPages
return reference;
}
public IClock Clock => LazyGetRequiredService(ref _clock);
protected IClock Clock => LazyGetRequiredService(ref _clock);
private IClock _clock;
public AlertList Alerts => AlertManager.Alerts;
protected AlertList Alerts => AlertManager.Alerts;
public IUnitOfWorkManager UnitOfWorkManager => LazyGetRequiredService(ref _unitOfWorkManager);
protected IUnitOfWorkManager UnitOfWorkManager => LazyGetRequiredService(ref _unitOfWorkManager);
private IUnitOfWorkManager _unitOfWorkManager;
protected Type ObjectMapperContext { get; set; }
public IObjectMapper ObjectMapper
protected IObjectMapper ObjectMapper
{
get
{
@ -76,16 +76,16 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.RazorPages
}
private IObjectMapper _objectMapper;
public IGuidGenerator GuidGenerator => LazyGetRequiredService(ref _guidGenerator);
protected IGuidGenerator GuidGenerator => LazyGetRequiredService(ref _guidGenerator);
private IGuidGenerator _guidGenerator;
public ILoggerFactory LoggerFactory => LazyGetRequiredService(ref _loggerFactory);
protected ILoggerFactory LoggerFactory => LazyGetRequiredService(ref _loggerFactory);
private ILoggerFactory _loggerFactory;
public IStringLocalizerFactory StringLocalizerFactory => LazyGetRequiredService(ref _stringLocalizerFactory);
protected IStringLocalizerFactory StringLocalizerFactory => LazyGetRequiredService(ref _stringLocalizerFactory);
private IStringLocalizerFactory _stringLocalizerFactory;
public IStringLocalizer L
protected IStringLocalizer L
{
get
{
@ -102,22 +102,22 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.RazorPages
protected Type LocalizationResourceType { get; set; }
public ICurrentUser CurrentUser => LazyGetRequiredService(ref _currentUser);
protected ICurrentUser CurrentUser => LazyGetRequiredService(ref _currentUser);
private ICurrentUser _currentUser;
public ICurrentTenant CurrentTenant => LazyGetRequiredService(ref _currentTenant);
protected ICurrentTenant CurrentTenant => LazyGetRequiredService(ref _currentTenant);
private ICurrentTenant _currentTenant;
public ISettingProvider SettingProvider => LazyGetRequiredService(ref _settingProvider);
protected ISettingProvider SettingProvider => LazyGetRequiredService(ref _settingProvider);
private ISettingProvider _settingProvider;
public IModelStateValidator ModelValidator => LazyGetRequiredService(ref _modelValidator);
protected IModelStateValidator ModelValidator => LazyGetRequiredService(ref _modelValidator);
private IModelStateValidator _modelValidator;
public IAuthorizationService AuthorizationService => LazyGetRequiredService(ref _authorizationService);
protected IAuthorizationService AuthorizationService => LazyGetRequiredService(ref _authorizationService);
private IAuthorizationService _authorizationService;
public IAlertManager AlertManager => LazyGetRequiredService(ref _alertManager);
protected IAlertManager AlertManager => LazyGetRequiredService(ref _alertManager);
private IAlertManager _alertManager;
protected IUnitOfWork CurrentUnitOfWork => UnitOfWorkManager?.Current;

24
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpController.cs

@ -43,11 +43,11 @@ namespace Volo.Abp.AspNetCore.Mvc
return reference;
}
public IUnitOfWorkManager UnitOfWorkManager => LazyGetRequiredService(ref _unitOfWorkManager);
protected IUnitOfWorkManager UnitOfWorkManager => LazyGetRequiredService(ref _unitOfWorkManager);
private IUnitOfWorkManager _unitOfWorkManager;
protected Type ObjectMapperContext { get; set; }
public IObjectMapper ObjectMapper
protected IObjectMapper ObjectMapper
{
get
{
@ -69,39 +69,39 @@ namespace Volo.Abp.AspNetCore.Mvc
}
private IObjectMapper _objectMapper;
public IGuidGenerator GuidGenerator => LazyGetRequiredService(ref _guidGenerator);
protected IGuidGenerator GuidGenerator => LazyGetRequiredService(ref _guidGenerator);
private IGuidGenerator _guidGenerator;
public ILoggerFactory LoggerFactory => LazyGetRequiredService(ref _loggerFactory);
protected ILoggerFactory LoggerFactory => LazyGetRequiredService(ref _loggerFactory);
private ILoggerFactory _loggerFactory;
protected ILogger Logger => _lazyLogger.Value;
private Lazy<ILogger> _lazyLogger => new Lazy<ILogger>(() => LoggerFactory?.CreateLogger(GetType().FullName) ?? NullLogger.Instance, true);
public ICurrentUser CurrentUser => LazyGetRequiredService(ref _currentUser);
protected ICurrentUser CurrentUser => LazyGetRequiredService(ref _currentUser);
private ICurrentUser _currentUser;
public ICurrentTenant CurrentTenant => LazyGetRequiredService(ref _currentTenant);
protected ICurrentTenant CurrentTenant => LazyGetRequiredService(ref _currentTenant);
private ICurrentTenant _currentTenant;
public IAuthorizationService AuthorizationService => LazyGetRequiredService(ref _authorizationService);
protected IAuthorizationService AuthorizationService => LazyGetRequiredService(ref _authorizationService);
private IAuthorizationService _authorizationService;
protected IUnitOfWork CurrentUnitOfWork => UnitOfWorkManager?.Current;
public IClock Clock => LazyGetRequiredService(ref _clock);
protected IClock Clock => LazyGetRequiredService(ref _clock);
private IClock _clock;
public IModelStateValidator ModelValidator => LazyGetRequiredService(ref _modelValidator);
protected IModelStateValidator ModelValidator => LazyGetRequiredService(ref _modelValidator);
private IModelStateValidator _modelValidator;
public IFeatureChecker FeatureChecker => LazyGetRequiredService(ref _featureChecker);
protected IFeatureChecker FeatureChecker => LazyGetRequiredService(ref _featureChecker);
private IFeatureChecker _featureChecker;
public IStringLocalizerFactory StringLocalizerFactory => LazyGetRequiredService(ref _stringLocalizerFactory);
protected IStringLocalizerFactory StringLocalizerFactory => LazyGetRequiredService(ref _stringLocalizerFactory);
private IStringLocalizerFactory _stringLocalizerFactory;
public IStringLocalizer L
protected IStringLocalizer L
{
get
{

2
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpViewComponent.cs

@ -30,7 +30,7 @@ namespace Volo.Abp.AspNetCore.Mvc
}
protected Type ObjectMapperContext { get; set; }
public IObjectMapper ObjectMapper
protected IObjectMapper ObjectMapper
{
get
{

30
framework/src/Volo.Abp.AspNetCore.SignalR/Volo/Abp/AspNetCore/SignalR/AbpHub.cs

@ -36,28 +36,28 @@ namespace Volo.Abp.AspNetCore.SignalR
return reference;
}
public ILoggerFactory LoggerFactory => LazyGetRequiredService(ref _loggerFactory);
protected ILoggerFactory LoggerFactory => LazyGetRequiredService(ref _loggerFactory);
private ILoggerFactory _loggerFactory;
protected ILogger Logger => _lazyLogger.Value;
private Lazy<ILogger> _lazyLogger => new Lazy<ILogger>(() => LoggerFactory?.CreateLogger(GetType().FullName) ?? NullLogger.Instance, true);
public ICurrentUser CurrentUser => LazyGetRequiredService(ref _currentUser);
protected ICurrentUser CurrentUser => LazyGetRequiredService(ref _currentUser);
private ICurrentUser _currentUser;
public ICurrentTenant CurrentTenant => LazyGetRequiredService(ref _currentTenant);
protected ICurrentTenant CurrentTenant => LazyGetRequiredService(ref _currentTenant);
private ICurrentTenant _currentTenant;
public IAuthorizationService AuthorizationService => LazyGetRequiredService(ref _authorizationService);
protected IAuthorizationService AuthorizationService => LazyGetRequiredService(ref _authorizationService);
private IAuthorizationService _authorizationService;
public IClock Clock => LazyGetRequiredService(ref _clock);
protected IClock Clock => LazyGetRequiredService(ref _clock);
private IClock _clock;
public IStringLocalizerFactory StringLocalizerFactory => LazyGetRequiredService(ref _stringLocalizerFactory);
protected IStringLocalizerFactory StringLocalizerFactory => LazyGetRequiredService(ref _stringLocalizerFactory);
private IStringLocalizerFactory _stringLocalizerFactory;
public IStringLocalizer L
protected IStringLocalizer L
{
get
{
@ -99,7 +99,7 @@ namespace Volo.Abp.AspNetCore.SignalR
}
}
public abstract class AbpHub<T> : Hub<T>
public abstract class AbpHub<T> : Hub<T>
where T : class
{
public IServiceProvider ServiceProvider { get; set; }
@ -124,28 +124,28 @@ namespace Volo.Abp.AspNetCore.SignalR
return reference;
}
public ILoggerFactory LoggerFactory => LazyGetRequiredService(ref _loggerFactory);
protected ILoggerFactory LoggerFactory => LazyGetRequiredService(ref _loggerFactory);
private ILoggerFactory _loggerFactory;
protected ILogger Logger => _lazyLogger.Value;
private Lazy<ILogger> _lazyLogger => new Lazy<ILogger>(() => LoggerFactory?.CreateLogger(GetType().FullName) ?? NullLogger.Instance, true);
public ICurrentUser CurrentUser => LazyGetRequiredService(ref _currentUser);
protected ICurrentUser CurrentUser => LazyGetRequiredService(ref _currentUser);
private ICurrentUser _currentUser;
public ICurrentTenant CurrentTenant => LazyGetRequiredService(ref _currentTenant);
protected ICurrentTenant CurrentTenant => LazyGetRequiredService(ref _currentTenant);
private ICurrentTenant _currentTenant;
public IAuthorizationService AuthorizationService => LazyGetRequiredService(ref _authorizationService);
protected IAuthorizationService AuthorizationService => LazyGetRequiredService(ref _authorizationService);
private IAuthorizationService _authorizationService;
public IClock Clock => LazyGetRequiredService(ref _clock);
protected IClock Clock => LazyGetRequiredService(ref _clock);
private IClock _clock;
public IStringLocalizerFactory StringLocalizerFactory => LazyGetRequiredService(ref _stringLocalizerFactory);
protected IStringLocalizerFactory StringLocalizerFactory => LazyGetRequiredService(ref _stringLocalizerFactory);
private IStringLocalizerFactory _stringLocalizerFactory;
public IStringLocalizer L
protected IStringLocalizer L
{
get
{

2
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/Claims/HttpContextCurrentPrincipalAccessor.cs

@ -13,7 +13,7 @@ namespace Volo.Abp.AspNetCore.Security.Claims
_httpContextAccessor = httpContextAccessor;
}
public override ClaimsPrincipal GetClaimsPrincipal()
protected override ClaimsPrincipal GetClaimsPrincipal()
{
return _httpContextAccessor.HttpContext?.User ?? base.GetClaimsPrincipal();
}

4
framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/BackgroundWorkerBase.cs

@ -35,7 +35,7 @@ namespace Volo.Abp.BackgroundWorkers
return reference;
}
public ILoggerFactory LoggerFactory => LazyGetRequiredService(ref _loggerFactory);
protected ILoggerFactory LoggerFactory => LazyGetRequiredService(ref _loggerFactory);
private ILoggerFactory _loggerFactory;
protected ILogger Logger => _lazyLogger.Value;
@ -58,4 +58,4 @@ namespace Volo.Abp.BackgroundWorkers
return GetType().FullName;
}
}
}
}

1
framework/src/Volo.Abp.BlobStoring.FileSystem/Volo.Abp.BlobStoring.FileSystem.csproj

@ -16,6 +16,7 @@
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.BlobStoring\Volo.Abp.BlobStoring.csproj" />
<PackageReference Include="Polly" Version="7.2.0" />
</ItemGroup>
</Project>

53
framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/FileSystemBlobProvider.cs

@ -1,5 +1,7 @@
using System.IO;
using System;
using System.IO;
using System.Threading.Tasks;
using Polly;
using Volo.Abp.DependencyInjection;
using Volo.Abp.IO;
@ -8,12 +10,12 @@ namespace Volo.Abp.BlobStoring.FileSystem
public class FileSystemBlobProvider : BlobProviderBase, ITransientDependency
{
protected IBlobFilePathCalculator FilePathCalculator { get; }
public FileSystemBlobProvider(IBlobFilePathCalculator filePathCalculator)
{
FilePathCalculator = filePathCalculator;
}
public override async Task SaveAsync(BlobProviderSaveArgs args)
{
var filePath = FilePathCalculator.Calculate(args);
@ -22,22 +24,27 @@ namespace Volo.Abp.BlobStoring.FileSystem
{
throw new BlobAlreadyExistsException($"Saving BLOB '{args.BlobName}' does already exists in the container '{args.ContainerName}'! Set {nameof(args.OverrideExisting)} if it should be overwritten.");
}
DirectoryHelper.CreateIfNotExists(Path.GetDirectoryName(filePath));
var fileMode = args.OverrideExisting
? FileMode.Create
: FileMode.CreateNew;
using (var fileStream = File.Open(filePath, fileMode, FileAccess.Write))
{
await args.BlobStream.CopyToAsync(
fileStream,
args.CancellationToken
);
await fileStream.FlushAsync();
}
await Policy.Handle<IOException>()
.WaitAndRetryAsync(2, retryCount => TimeSpan.FromSeconds(retryCount))
.ExecuteAsync(async () =>
{
using (var fileStream = File.Open(filePath, fileMode, FileAccess.Write))
{
await args.BlobStream.CopyToAsync(
fileStream,
args.CancellationToken
);
await fileStream.FlushAsync();
}
});
}
public override Task<bool> DeleteAsync(BlobProviderDeleteArgs args)
@ -52,21 +59,31 @@ namespace Volo.Abp.BlobStoring.FileSystem
return ExistsAsync(filePath);
}
public override Task<Stream> GetOrNullAsync(BlobProviderGetArgs args)
public override async Task<Stream> GetOrNullAsync(BlobProviderGetArgs args)
{
var filePath = FilePathCalculator.Calculate(args);
if (!File.Exists(filePath))
{
return Task.FromResult<Stream>(null);
return null;
}
return Task.FromResult<Stream>(File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read));
return await Policy.Handle<IOException>()
.WaitAndRetryAsync(2, retryCount => TimeSpan.FromSeconds(retryCount))
.ExecuteAsync(async () =>
{
using (var fileStream = File.OpenRead(filePath))
{
var memoryStream = new MemoryStream();
await fileStream.CopyToAsync(memoryStream, args.CancellationToken);
return memoryStream;
}
});
}
protected virtual Task<bool> ExistsAsync(string filePath)
{
return Task.FromResult(File.Exists(filePath));
}
}
}
}

5
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerFactory.cs

@ -1,5 +1,4 @@
using System.Threading;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Threading;
@ -27,7 +26,7 @@ namespace Volo.Abp.BlobStoring
ProviderSelector = providerSelector;
}
public virtual IBlobContainer Create(string name, CancellationToken cancellationToken = default)
public virtual IBlobContainer Create(string name)
{
var configuration = ConfigurationProvider.Get(name);

10
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerFactoryExtensions.cs

@ -1,6 +1,4 @@
using System.Threading;
namespace Volo.Abp.BlobStoring
namespace Volo.Abp.BlobStoring
{
public static class BlobContainerFactoryExtensions
{
@ -13,13 +11,11 @@ namespace Volo.Abp.BlobStoring
/// The container object.
/// </returns>
public static IBlobContainer Create<TContainer>(
this IBlobContainerFactory blobContainerFactory,
CancellationToken cancellationToken = default
this IBlobContainerFactory blobContainerFactory
)
{
return blobContainerFactory.Create(
BlobContainerNameAttribute.GetContainerName<TContainer>(),
cancellationToken
BlobContainerNameAttribute.GetContainerName<TContainer>()
);
}
}

4
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/DefaultContainer.cs

@ -1,8 +1,8 @@
namespace Volo.Abp.BlobStoring
{
[BlobContainerName("Default")]
[BlobContainerName(Name)]
public class DefaultContainer
{
public const string Name = "Default";
}
}

5
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobContainerConfigurationProvider.cs

@ -2,6 +2,11 @@
{
public interface IBlobContainerConfigurationProvider
{
/// <summary>
/// Gets a <see cref="BlobContainerConfiguration"/> for the given container <paramref name="name"/>.
/// </summary>
/// <param name="name">The name of the container</param>
/// <returns>The configuration that should be used for the container</returns>
BlobContainerConfiguration Get(string name);
}
}

8
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobContainerFactory.cs

@ -1,6 +1,4 @@
using System.Threading;
namespace Volo.Abp.BlobStoring
namespace Volo.Abp.BlobStoring
{
public interface IBlobContainerFactory
{
@ -8,13 +6,11 @@ namespace Volo.Abp.BlobStoring
/// Gets a named container.
/// </summary>
/// <param name="name">The name of the container</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>
/// The container object.
/// </returns>
IBlobContainer Create(
string name,
CancellationToken cancellationToken = default
string name
);
}
}

1
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs

@ -226,6 +226,7 @@ namespace Volo.Abp.Cli.Commands
sb.AppendLine(" abp new Acme.BookStore -d mongodb -o d:\\my-project");
sb.AppendLine(" abp new Acme.BookStore -t module");
sb.AppendLine(" abp new Acme.BookStore -t module --no-ui");
sb.AppendLine(" abp new Acme.BookStore -t console");
sb.AppendLine(" abp new Acme.BookStore -ts \"D:\\localTemplate\\abp\"");
sb.AppendLine(" abp new Acme.BookStore -csf false");
sb.AppendLine(" abp new Acme.BookStore --local-framework-ref --abp-path \"D:\\github\\abp\"");

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs

@ -55,7 +55,7 @@ namespace Volo.Abp.Cli.ProjectBuilding
string templateSource = null)
{
DirectoryHelper.CreateIfNotExists(CliPaths.TemplateCache);
var latestVersion = await GetLatestSourceCodeVersionAsync(name, type);
var latestVersion = version ?? await GetLatestSourceCodeVersionAsync(name, type);
if (version == null)
{

3
framework/src/Volo.Abp.Core/System/IO/AbpStreamExtensions.cs

@ -9,6 +9,7 @@ namespace System.IO
{
using (var memoryStream = new MemoryStream())
{
stream.Position = 0;
stream.CopyTo(memoryStream);
return memoryStream.ToArray();
}
@ -18,6 +19,7 @@ namespace System.IO
{
using (var memoryStream = new MemoryStream())
{
stream.Position = 0;
await stream.CopyToAsync(memoryStream, cancellationToken);
return memoryStream.ToArray();
}
@ -25,6 +27,7 @@ namespace System.IO
public static Task CopyToAsync(this Stream stream, Stream destination, CancellationToken cancellationToken)
{
stream.Position = 0;
return stream.CopyToAsync(
destination,
81920, //this is already the default value, but needed to set to be able to pass the cancellationToken

24
framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs

@ -57,11 +57,11 @@ namespace Volo.Abp.Application.Services
public List<string> AppliedCrossCuttingConcerns { get; } = new List<string>();
public IUnitOfWorkManager UnitOfWorkManager => LazyGetRequiredService(ref _unitOfWorkManager);
protected IUnitOfWorkManager UnitOfWorkManager => LazyGetRequiredService(ref _unitOfWorkManager);
private IUnitOfWorkManager _unitOfWorkManager;
protected Type ObjectMapperContext { get; set; }
public IObjectMapper ObjectMapper
protected IObjectMapper ObjectMapper
{
get
{
@ -85,31 +85,31 @@ namespace Volo.Abp.Application.Services
public IGuidGenerator GuidGenerator { get; set; }
public ILoggerFactory LoggerFactory => LazyGetRequiredService(ref _loggerFactory);
protected ILoggerFactory LoggerFactory => LazyGetRequiredService(ref _loggerFactory);
private ILoggerFactory _loggerFactory;
public ICurrentTenant CurrentTenant => LazyGetRequiredService(ref _currentTenant);
protected ICurrentTenant CurrentTenant => LazyGetRequiredService(ref _currentTenant);
private ICurrentTenant _currentTenant;
public ICurrentUser CurrentUser => LazyGetRequiredService(ref _currentUser);
protected ICurrentUser CurrentUser => LazyGetRequiredService(ref _currentUser);
private ICurrentUser _currentUser;
public ISettingProvider SettingProvider => LazyGetRequiredService(ref _settingProvider);
protected ISettingProvider SettingProvider => LazyGetRequiredService(ref _settingProvider);
private ISettingProvider _settingProvider;
public IClock Clock => LazyGetRequiredService(ref _clock);
protected IClock Clock => LazyGetRequiredService(ref _clock);
private IClock _clock;
public IAuthorizationService AuthorizationService => LazyGetRequiredService(ref _authorizationService);
protected IAuthorizationService AuthorizationService => LazyGetRequiredService(ref _authorizationService);
private IAuthorizationService _authorizationService;
public IFeatureChecker FeatureChecker => LazyGetRequiredService(ref _featureChecker);
protected IFeatureChecker FeatureChecker => LazyGetRequiredService(ref _featureChecker);
private IFeatureChecker _featureChecker;
public IStringLocalizerFactory StringLocalizerFactory => LazyGetRequiredService(ref _stringLocalizerFactory);
protected IStringLocalizerFactory StringLocalizerFactory => LazyGetRequiredService(ref _stringLocalizerFactory);
private IStringLocalizerFactory _stringLocalizerFactory;
public IStringLocalizer L
protected IStringLocalizer L
{
get
{
@ -175,4 +175,4 @@ namespace Volo.Abp.Application.Services
return localizer;
}
}
}
}

12
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Services/DomainService.cs

@ -28,23 +28,23 @@ namespace Volo.Abp.Domain.Services
return reference;
}
public IClock Clock => LazyGetRequiredService(ref _clock);
protected IClock Clock => LazyGetRequiredService(ref _clock);
private IClock _clock;
public IGuidGenerator GuidGenerator { get; set; }
public ILoggerFactory LoggerFactory => LazyGetRequiredService(ref _loggerFactory);
protected ILoggerFactory LoggerFactory => LazyGetRequiredService(ref _loggerFactory);
private ILoggerFactory _loggerFactory;
public ICurrentTenant CurrentTenant => LazyGetRequiredService(ref _currentTenant);
protected ICurrentTenant CurrentTenant => LazyGetRequiredService(ref _currentTenant);
private ICurrentTenant _currentTenant;
protected ILogger Logger => _lazyLogger.Value;
private Lazy<ILogger> _lazyLogger => new Lazy<ILogger>(() => LoggerFactory?.CreateLogger(GetType().FullName) ?? NullLogger.Instance, true);
protected DomainService()
{
GuidGenerator = SimpleGuidGenerator.Instance;
}
}
}
}

2
framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/ThreadCurrentPrincipalAccessor.cs

@ -11,7 +11,7 @@ namespace Volo.Abp.Security.Claims
private readonly AsyncLocal<ClaimsPrincipal> _currentPrincipal = new AsyncLocal<ClaimsPrincipal>();
public virtual ClaimsPrincipal GetClaimsPrincipal()
protected virtual ClaimsPrincipal GetClaimsPrincipal()
{
return Thread.CurrentPrincipal as ClaimsPrincipal;
}

4
framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json

@ -44,7 +44,7 @@
"PagerInfoEmpty": "显示0个条目中的0到0",
"PagerInfoFiltered": "(从 _MAX_ 总条目中过滤掉)",
"NoDataAvailableInDatatable": "表中没有数据",
"PagerShowMenuEntries": "显示 _MENU_ 实体",
"PagerShowMenuEntries": "显示 _MENU_ 条数据",
"DatatableActionDropdownDefaultText": "操作",
"ChangePassword": "修改密码",
"PersonalInfo": "个人信息",
@ -58,4 +58,4 @@
"GoBack": "返回",
"Search" : "搜索"
}
}
}

6
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Borders.cshtml

@ -34,7 +34,7 @@
<h2>Borders</h2>
<p>Based on <a href="https://getbootstrap.com/docs/4.1/components/border/" target="_blank"> Bootstrap Border</a>.</p>
<p>Based on <a href="https://getbootstrap.com/docs/4.1/utilities/borders/" target="_blank"> Bootstrap Border</a>.</p>
<h4>Border</h4>
@ -145,7 +145,7 @@
&lt;span abp-border=&quot;Top_Secondary&quot;&gt;&lt;/span&gt;
&lt;span abp-border=&quot;Right_Success&quot;&gt;&lt;/span&gt;
&lt;span abp-border=&quot;Bottom_Danger&quot;&gt;&lt;/span&gt;
&lt;span abp-border=&quot;bottom_Warning&quot;&gt;&lt;/span&gt;
&lt;span abp-border=&quot;Bottom_Warning&quot;&gt;&lt;/span&gt;
&lt;span abp-border=&quot;Left_Info&quot;&gt;&lt;/span&gt;
&lt;span abp-border=&quot;Top_Light&quot;&gt;&lt;/span&gt;
&lt;span abp-border=&quot;Right_Dark&quot;&gt;&lt;/span&gt;
@ -212,4 +212,4 @@
</abp-tab>
</abp-tabs>
</div>
</div>
</div>

4
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Breadcrumbs.cshtml

@ -24,7 +24,7 @@
<h2>Breadcrumbs</h2>
<p>Based on <a href="https://getbootstrap.com/docs/4.1/components/breadcrumbs/" target="_blank"> Bootstrap Breadcrumb</a>.</p>
<p>Based on <a href="https://getbootstrap.com/docs/4.1/components/breadcrumb/" target="_blank"> Bootstrap Breadcrumb</a>.</p>
<h4>Example</h4>
@ -93,4 +93,4 @@
</abp-tab>
</abp-tabs>
</div>
</div>
</div>

2
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Carousel.cshtml

@ -25,7 +25,7 @@
<h2>Carousels</h2>
<p>Based on <a href="https://getbootstrap.com/docs/4.1/content/images/" target="_blank"> Bootstrap Carousel</a>.</p>
<p>Based on <a href="https://getbootstrap.com/docs/4.1/components/carousel/" target="_blank"> Bootstrap Carousel</a>.</p>
<h4>Slides only</h4>

2
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Tables.cshtml

@ -25,7 +25,7 @@
<h2>Tables</h2>
<p>Based on <a href="https://getbootstrap.com/docs/4.1/content/Tables/" target="_blank"> Bootstrap Tables</a>.</p>
<p>Based on <a href="https://getbootstrap.com/docs/4.1/content/tables/" target="_blank"> Bootstrap Tables</a>.</p>
<h4>Examples</h4>

13
modules/account/src/Volo.Abp.Account.Web/Pages/Account/AccountPage.cs

@ -1,13 +0,0 @@
using Microsoft.AspNetCore.Mvc.Localization;
using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Volo.Abp.Account.Localization;
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;
namespace Volo.Abp.Account.Web.Pages.Account
{
public abstract class AccountPage : AbpPage
{
[RazorInject]
public IHtmlLocalizer<AccountResource> L { get; set; }
}
}

4
modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml

@ -1,8 +1,10 @@
@page
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Abp.Account.Localization
@using Volo.Abp.Account.Settings
@using Volo.Abp.Settings
@model Volo.Abp.Account.Web.Pages.Account.LoginModel
@inherits Volo.Abp.Account.Web.Pages.Account.AccountPage
@inject IHtmlLocalizer<AccountResource> L
@inject Volo.Abp.Settings.ISettingProvider SettingProvider
@if (Model.EnableLocalLogin)
{

3
modules/account/src/Volo.Abp.Account.Web/Pages/Account/Logout.cshtml

@ -1,3 +1,2 @@
@page "/Account/Logout"
@inherits Volo.Abp.Account.Web.Pages.Account.AccountPage
@model Volo.Abp.Account.Web.Pages.Account.LogoutModel
@model Volo.Abp.Account.Web.Pages.Account.LogoutModel

6
modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml

@ -1,11 +1,13 @@
@page
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Abp.Account.Localization
@using Volo.Abp.Account.Web.Pages.Account
@using Volo.Abp.Identity.Settings
@using Volo.Abp.Settings
@using Volo.Abp.AspNetCore.Mvc.UI.Theming
@inject ISettingProvider SettingManager
@inject IThemeManager ThemeManager
@inherits Volo.Abp.Account.Web.Pages.Account.AccountPage
@inject IHtmlLocalizer<AccountResource> L
@model ManageModel
@{
Layout = ThemeManager.CurrentTheme.GetApplicationLayout();
@ -54,4 +56,4 @@
</abp-tab>
</abp-tabs>
</abp-card-body>
</abp-card>
</abp-card>

4
modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml

@ -1,6 +1,8 @@
@page
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Abp.Account.Localization
@model Volo.Abp.Account.Web.Pages.Account.RegisterModel
@inherits Volo.Abp.Account.Web.Pages.Account.AccountPage
@inject IHtmlLocalizer<AccountResource> L
<div class="card mt-3 shadow-sm rounded">
<div class="card-body p-5">

6
modules/account/src/Volo.Abp.Account.Web/Pages/Account/SendSecurityCode.cshtml

@ -1,6 +1,8 @@
@page
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Abp.Account.Localization
@using Volo.Abp.Account.Web.Pages.Account
@inherits AccountPage
@model SendSecurityCodeModel
@inject IHtmlLocalizer<AccountResource> L
<h2>Send security code!</h2>
<p>TODO: This page is under construction.</p>
<p>TODO: This page is under construction.</p>

55
modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/DatabaseBlobProvider.cs

@ -26,8 +26,11 @@ namespace Volo.Abp.BlobStoring.Database
{
var container = await GetOrCreateContainerAsync(args.ContainerName, args.CancellationToken);
var blob = await DatabaseBlobRepository.FindAsync(container.Id, args.BlobName,
args.CancellationToken);
var blob = await DatabaseBlobRepository.FindAsync(
container.Id,
args.BlobName,
args.CancellationToken
);
var content = await args.BlobStream.GetAllBytesAsync(args.CancellationToken);
@ -40,58 +43,72 @@ namespace Volo.Abp.BlobStoring.Database
}
blob.SetContent(content);
await DatabaseBlobRepository.UpdateAsync(blob);
await DatabaseBlobRepository.UpdateAsync(blob, autoSave: true);
}
else
{
blob = new DatabaseBlob(GuidGenerator.Create(), container.Id, args.BlobName, content);
await DatabaseBlobRepository.InsertAsync(blob);
await DatabaseBlobRepository.InsertAsync(blob, autoSave: true);
}
}
public override async Task<bool> DeleteAsync(BlobProviderDeleteArgs args)
{
var container =
await DatabaseBlobContainerRepository.FindAsync(args.ContainerName,
args.CancellationToken);
var container = await DatabaseBlobContainerRepository.FindAsync(
args.ContainerName,
args.CancellationToken
);
if (container == null)
{
return false;
}
return await DatabaseBlobRepository.DeleteAsync(container.Id, args.BlobName,
args.CancellationToken);
return await DatabaseBlobRepository.DeleteAsync(
container.Id,
args.BlobName,
autoSave: true,
cancellationToken: args.CancellationToken
);
}
public override async Task<bool> ExistsAsync(BlobProviderExistsArgs args)
{
var container =
await DatabaseBlobContainerRepository.FindAsync(args.ContainerName,
args.CancellationToken);
var container = await DatabaseBlobContainerRepository.FindAsync(
args.ContainerName,
args.CancellationToken
);
if (container == null)
{
return false;
}
return await DatabaseBlobRepository.ExistsAsync(container.Id, args.BlobName,
args.CancellationToken);
return await DatabaseBlobRepository.ExistsAsync(
container.Id,
args.BlobName,
args.CancellationToken
);
}
public override async Task<Stream> GetOrNullAsync(BlobProviderGetArgs args)
{
var container =
await DatabaseBlobContainerRepository.FindAsync(args.ContainerName,
args.CancellationToken);
var container = await DatabaseBlobContainerRepository.FindAsync(
args.ContainerName,
args.CancellationToken
);
if (container == null)
{
return null;
}
var blob = await DatabaseBlobRepository.FindAsync(container.Id, args.BlobName,
args.CancellationToken);
var blob = await DatabaseBlobRepository.FindAsync(
container.Id,
args.BlobName,
args.CancellationToken
);
if (blob == null)
{

2
modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/IDatabaseBlobRepository.cs

@ -12,6 +12,6 @@ namespace Volo.Abp.BlobStoring.Database
Task<bool> ExistsAsync(Guid containerId, [NotNull] string name, CancellationToken cancellationToken = default);
Task<bool> DeleteAsync(Guid containerId, [NotNull] string name, CancellationToken cancellationToken = default);
Task<bool> DeleteAsync(Guid containerId, [NotNull] string name, bool autoSave = false, CancellationToken cancellationToken = default);
}
}

10
modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/Volo/Abp/BlobStoring/Database/EntityFrameworkCore/EfCoreDatabaseBlobRepository.cs

@ -7,9 +7,10 @@ using Volo.Abp.EntityFrameworkCore;
namespace Volo.Abp.BlobStoring.Database.EntityFrameworkCore
{
public class EfCoreDatabaseBlobRepository : EfCoreRepository<IBlobStoringDbContext, DatabaseBlob, Guid>, IDatabaseBlobRepository
public class EfCoreDatabaseBlobRepository : EfCoreRepository<IBlobStoringDbContext, DatabaseBlob, Guid>,
IDatabaseBlobRepository
{
public EfCoreDatabaseBlobRepository(IDbContextProvider<IBlobStoringDbContext> dbContextProvider)
public EfCoreDatabaseBlobRepository(IDbContextProvider<IBlobStoringDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
@ -38,15 +39,18 @@ namespace Volo.Abp.BlobStoring.Database.EntityFrameworkCore
public virtual async Task<bool> DeleteAsync(
Guid containerId,
string name,
bool autoSave = false,
CancellationToken cancellationToken = default)
{
//TODO: Should extract this logic to out of the repository and remove this method completely
var blob = await FindAsync(containerId, name, cancellationToken);
if (blob == null)
{
return false;
}
await base.DeleteAsync(blob.Id, cancellationToken: GetCancellationToken(cancellationToken));
await base.DeleteAsync(blob, autoSave, cancellationToken: GetCancellationToken(cancellationToken));
return true;
}
}

8
modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo/Abp/BlobStoring/Database/MongoDB/MongoDbDatabaseBlobRepository.cs

@ -29,7 +29,11 @@ namespace Volo.Abp.BlobStoring.Database.MongoDB
GetCancellationToken(cancellationToken));
}
public virtual async Task<bool> DeleteAsync(Guid containerId, string name, CancellationToken cancellationToken = default)
public virtual async Task<bool> DeleteAsync(
Guid containerId,
string name,
bool autoSave = false,
CancellationToken cancellationToken = default)
{
var blob = await FindAsync(containerId, name, cancellationToken);
@ -38,7 +42,7 @@ namespace Volo.Abp.BlobStoring.Database.MongoDB
return false;
}
await base.DeleteAsync(blob, cancellationToken: GetCancellationToken(cancellationToken));
await base.DeleteAsync(blob, autoSave, cancellationToken: GetCancellationToken(cancellationToken));
return true;
}
}

2
modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.TestBase/Security/FakeCurrentPrincipalAccessor.cs

@ -8,7 +8,7 @@ namespace Volo.Abp.BlobStoring.Database.Security
[Dependency(ReplaceServices = true)]
public class FakeCurrentPrincipalAccessor : ThreadCurrentPrincipalAccessor
{
public override ClaimsPrincipal GetClaimsPrincipal()
protected override ClaimsPrincipal GetClaimsPrincipal()
{
return GetPrincipal();
}

4
modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/Create.cshtml

@ -1,8 +1,10 @@
@page
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@using Volo.Blogging.Pages.Blog
@inherits BloggingPage
@model Volo.Blogging.Pages.Admin.Blogs.CreateModel
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Blogging.Localization
@inject IHtmlLocalizer<BloggingResource> L
@{
Layout = null;
}

6
modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/Edit.cshtml

@ -1,8 +1,10 @@
@page
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@using Volo.Blogging.Pages.Blog
@inherits BloggingPage
@model Volo.Blogging.Pages.Admin.Blogs.EditModel
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Blogging.Localization
@inject IHtmlLocalizer<BloggingResource> L
@{
Layout = null;
}
@ -17,4 +19,4 @@
<abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)">
</abp-modal-footer>
</abp-modal>
</abp-dynamic-form>
</abp-dynamic-form>

4
modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/Index.cshtml

@ -2,8 +2,10 @@
@using Microsoft.AspNetCore.Authorization
@using Volo.Blogging
@using Volo.Blogging.Pages.Blog
@inherits BloggingPage
@model Volo.Blogging.Pages.Admin.Blogs.IndexModel
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Blogging.Localization
@inject IHtmlLocalizer<BloggingResource> L
@inject IAuthorizationService Authorization
@{
ViewBag.PageTitle = "Blogs";

14
modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/BloggingPage.cs

@ -1,19 +1,17 @@
using System;
using System;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Localization;
using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;
using Volo.Blogging.Localization;
using Markdig;
using Volo.Abp.DependencyInjection;
using Volo.Blogging.Localization;
namespace Volo.Blogging.Pages.Blog
{
public abstract class BloggingPage : AbpPage
public class BloggingPageHelper : ITransientDependency
{
[RazorInject]
public IHtmlLocalizer<BloggingResource> L { get; set; }
public const string DefaultTitle = "Blog";
@ -30,7 +28,7 @@ namespace Volo.Blogging.Pages.Blog
return title;
}
public string GetShortContent(string content)
public string GetShortContent(string content)
{
var html = RenderMarkdownToHtmlAsString(content);
var plainText = Regex.Replace(html, "<[^>]*>", "");
@ -49,7 +47,7 @@ namespace Volo.Blogging.Pages.Blog
{
shortContent.Append($" {line}");
}
if(shortContent.Length >= MaxShortContentLength)
{
return shortContent.ToString().Substring(0, MaxShortContentLength) + "...";

4
modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Index.cshtml

@ -1,6 +1,8 @@
@page
@using Volo.Blogging.Pages.Blog
@inherits BloggingPage
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Blogging.Localization
@inject IHtmlLocalizer<BloggingResource> L
@model IndexModel
@{
ViewBag.PageTitle = "Blogs";

15
modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Detail.cshtml

@ -1,5 +1,4 @@
@page
@inherits Volo.Blogging.Pages.Blog.BloggingPage
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Http.Extensions
@using Microsoft.Extensions.Options
@ -11,6 +10,12 @@
@using Volo.Blogging.SocialMedia
@inject IAuthorizationService Authorization
@inject IOptionsSnapshot<BloggingTwitterOptions> twitterOptions
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Blogging.Localization
@using Volo.Blogging.Pages.Blog
@inject IHtmlLocalizer<BloggingResource> L
@inject BloggingPageHelper BloggingPageHelper
@inject ICurrentUser CurrentUser
@model DetailModel
@{
ViewBag.Title = Model.Post.Title;
@ -67,7 +72,7 @@
<div class="col pl-1">
@if (Model.Post.Writer != null)
{
<h5 class="mt-2 mb-1">@(Model.Post.Writer.UserName) <span>@ConvertDatetimeToTimeAgo(Model.Post.CreationTime)</span></h5>
<h5 class="mt-2 mb-1">@(Model.Post.Writer.UserName) <span>@BloggingPageHelper.ConvertDatetimeToTimeAgo(Model.Post.CreationTime)</span></h5>
}
@ -108,7 +113,7 @@
<div class="col-12 col-md-8 col-lg-7 mx-auto">
<section class="post-content">
<p>
@Html.Raw(RenderMarkdownToHtml(Model.Post.Content))
@Html.Raw(BloggingPageHelper.RenderMarkdownToHtml(Model.Post.Content))
</p>
</section>
</div>
@ -164,7 +169,7 @@
<div class="media-body">
<h5 class="comment-owner">
@(commentWithRepliesDto.Comment.Writer == null ? "" : commentWithRepliesDto.Comment.Writer.UserName)
<span>@ConvertDatetimeToTimeAgo(commentWithRepliesDto.Comment.CreationTime)</span>
<span>@BloggingPageHelper.ConvertDatetimeToTimeAgo(commentWithRepliesDto.Comment.CreationTime)</span>
</h5>
<p id="@commentWithRepliesDto.Comment.Id">
@commentWithRepliesDto.Comment.Text
@ -243,7 +248,7 @@
<div class="media-body">
<h5 class="comment-owner">
@(reply.Writer == null ? "" : reply.Writer.UserName)
<span>@ConvertDatetimeToTimeAgo(reply.CreationTime)</span>
<span>@BloggingPageHelper.ConvertDatetimeToTimeAgo(reply.CreationTime)</span>
</h5>
<p id="@reply.Id">
@reply.Text

20
modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Edit.cshtml

@ -2,8 +2,10 @@
@using Volo.Abp.AspNetCore.Mvc.UI.Packages.TuiEditor
@using Volo.Blogging.Pages.Blog.Posts
@using Volo.Blogging.Posts
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Blogging.Localization
@inject IHtmlLocalizer<BloggingResource> L
@model EditModel
@inherits Volo.Blogging.Pages.Blog.BloggingPage
@{
ViewBag.PageTitle = "Blog";
}
@ -18,19 +20,19 @@
<abp-script type="@typeof(TuiEditorScriptContributor)" />
<abp-script src="/Pages/Blogs/Posts/edit.js" />
</abp-script-bundle>
}
}
<div id="edit-post-container">
<div class="container py-5">
<div class="row">
<div class="col-12 col-md-8 col-lg-7 mx-auto">
<div class="card">
<div class="card-body">
<div class="card-body">
<form method="post" id="edit-post-form">
<abp-input asp-for="Post.Title" auto-focus="true" />
<abp-alert
dismissible="true"
style="display: none"
<abp-alert
dismissible="true"
style="display: none"
id="title-length-warning"
data-max-length="@PostConsts.MaxTitleLengthToBeSeoFriendly">
@L["TitleLengthWarning"]
@ -38,7 +40,7 @@
<abp-input asp-for="Post.Url" />
<abp-input asp-for="Post.CoverImage" />
<abp-row>
<abp-column size-sm="_9">
<div class="form-group">
@ -87,5 +89,5 @@
</div>
</div>
</div>
</div>
</div>
</div>

18
modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Index.cshtml

@ -1,11 +1,15 @@
@page
@inherits Volo.Blogging.Pages.Blog.BloggingPage
@using Microsoft.AspNetCore.Authorization
@using Volo.Abp.AspNetCore.Mvc.UI.Packages.OwlCarousel
@using Volo.Blogging
@using Volo.Blogging.Pages.Blog.Posts
@inject IAuthorizationService Authorization
@model Volo.Blogging.Pages.Blog.Posts.IndexModel
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Blogging.Localization
@using Volo.Blogging.Pages.Blog
@using IndexModel = Volo.Blogging.Pages.Blog.Posts.IndexModel
@inject IHtmlLocalizer<BloggingResource> L
@inject BloggingPageHelper BloggingPageHelper
@{
ViewBag.Title = "Blog";
}
@ -42,7 +46,7 @@
<a asp-page="./Detail" asp-route-postUrl="@post.Url" asp-route-blogShortName="@Model.BlogShortName">@post.Title</a>
</h2>
<p class="article-sum">
@Html.Raw(GetShortContent(post.Content))
@Html.Raw(BloggingPageHelper.GetShortContent(post.Content))
</p>
<a asp-page="./Detail" asp-route-postUrl="@post.Url" asp-route-blogShortName="@Model.BlogShortName" class="read-more-btn">Continue Reading &#8594;</a>
@ -57,7 +61,7 @@
<img gravatar-email="@post.Writer.Email" default-image="Identicon" class="article-avatar" />
</div>
<div class="col pl-1">
<h5 class="mt-2 mb-1">@post.Writer.UserName <span>@ConvertDatetimeToTimeAgo(post.CreationTime)</span></h5>
<h5 class="mt-2 mb-1">@post.Writer.UserName <span>@BloggingPageHelper.ConvertDatetimeToTimeAgo(post.CreationTime)</span></h5>
<i class="fa fa-eye"></i> @L["WiewsWithCount", post.ReadCount]
@*<span class="vs-seperator">|</span>
<i class="fa fa-comment"></i> @L["CommentWithCount", post.CommentCount]*@
@ -113,7 +117,7 @@
<div class="user-card">
@if (post.Writer != null)
{
<h5 class="mt-2 mb-1">@post.Writer.UserName <span>@ConvertDatetimeToTimeAgo(post.CreationTime)</span></h5>
<h5 class="mt-2 mb-1">@post.Writer.UserName <span>@BloggingPageHelper.ConvertDatetimeToTimeAgo(post.CreationTime)</span></h5>
}
<i class="fa fa-eye"></i> @L["WiewsWithCount", post.ReadCount]
@*<span class="vs-seperator">|</span>
@ -161,7 +165,7 @@
<a asp-page="./Detail" asp-route-postUrl="@post.Url" asp-route-blogShortName="@Model.BlogShortName">@post.Title</a>
</h3>
<p>
@Html.Raw(GetShortContent(post.Content))
@Html.Raw(BloggingPageHelper.GetShortContent(post.Content))
</p>
<a asp-page="./Detail" asp-route-postUrl="@post.Url" asp-route-blogShortName="@Model.BlogShortName" class="read-more-btn">Continue Reading &#8594;</a>
@ -175,7 +179,7 @@
<img gravatar-email="@post.Writer.Email" default-image="Identicon" class="article-avatar" />
</div>
<div class="col pl-1">
<h5 class="mt-2 mb-1">@post.Writer.UserName <span>@ConvertDatetimeToTimeAgo(post.CreationTime)</span></h5>
<h5 class="mt-2 mb-1">@post.Writer.UserName <span>@BloggingPageHelper.ConvertDatetimeToTimeAgo(post.CreationTime)</span></h5>
<i class="fa fa-eye"></i> @L["WiewsWithCount", post.ReadCount]
@*<span class="vs-seperator">|</span>
<i class="fa fa-comment"></i> @L["CommentWithCount", post.CommentCount]*@

4
modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/New.cshtml

@ -2,8 +2,10 @@
@using Volo.Abp.AspNetCore.Mvc.UI.Packages.TuiEditor
@using Volo.Blogging.Pages.Blog.Posts
@using Volo.Blogging.Posts
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Blogging.Localization
@inject IHtmlLocalizer<BloggingResource> L
@model NewModel
@inherits Volo.Blogging.Pages.Blog.BloggingPage
@{
ViewBag.PageTitle = "Blog";
}

13
modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/DocsAdminPage.cs

@ -1,13 +0,0 @@
using Microsoft.AspNetCore.Mvc.Localization;
using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;
using Volo.Docs.Localization;
namespace Volo.Docs.Admin.Pages.Docs.Admin
{
public abstract class DocsAdminPage : AbpPage
{
[RazorInject]
public IHtmlLocalizer<DocsResource> L { get; set; }
}
}

6
modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Create.cshtml

@ -1,8 +1,10 @@
@page
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Docs.Localization
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@using Volo.Docs.Admin.Pages.Docs.Admin.Projects
@inherits Volo.Docs.Admin.Pages.Docs.Admin.DocsAdminPage
@model Volo.Docs.Admin.Pages.Docs.Admin.Projects.CreateModel
@inject IHtmlLocalizer<DocsResource> L
@{
Layout = null;
}
@ -19,4 +21,4 @@
</abp-modal-footer>
</abp-modal>
</abp-dynamic-form>
}
}

6
modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Edit.cshtml

@ -1,8 +1,10 @@
@page
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Docs.Localization
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@using Volo.Docs.Admin.Pages.Docs.Admin.Projects
@inherits Volo.Docs.Admin.Pages.Docs.Admin.DocsAdminPage
@model Volo.Docs.Admin.Pages.Docs.Admin.Projects.EditModel
@inject IHtmlLocalizer<DocsResource> L
@{
Layout = null;
}
@ -19,4 +21,4 @@
</abp-modal-footer>
</abp-modal>
</abp-dynamic-form>
}
}

4
modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Index.cshtml

@ -3,8 +3,10 @@
@using Volo.Abp.AspNetCore.Mvc.UI.Layout
@using Volo.Docs.Admin
@using Volo.Docs.Admin.Navigation
@inherits Volo.Docs.Admin.Pages.Docs.Admin.DocsAdminPage
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Docs.Localization
@model Volo.Docs.Admin.Pages.Docs.Admin.Projects.IndexModel
@inject IHtmlLocalizer<DocsResource> L
@inject IAuthorizationService Authorization
@{
ViewBag.PageTitle = "Projects";

4
modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Pull.cshtml

@ -1,8 +1,10 @@
@page
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Docs.Localization
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@using Volo.Docs.Admin.Pages.Docs.Admin.Projects
@inherits Volo.Docs.Admin.Pages.Docs.Admin.DocsAdminPage
@model Volo.Docs.Admin.Pages.Docs.Admin.Projects.PullModel
@inject IHtmlLocalizer<DocsResource> L
@{
Layout = null;
}

5
modules/docs/src/Volo.Docs.Application/Volo/Docs/DocsApplicationModule.cs

@ -1,7 +1,9 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Volo.Abp.AutoMapper;
using Volo.Abp.Caching;
using Volo.Abp.Modularity;
using Volo.Docs.Documents;
namespace Volo.Docs
{
@ -15,10 +17,13 @@ namespace Volo.Docs
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAutoMapperObjectMapper<DocsApplicationModule>();
Configure<AbpAutoMapperOptions>(options =>
{
options.AddProfile<DocsApplicationAutoMapperProfile>(validate: true);
});
context.Services.TryAddSingleton<INavigationTreePostProcessor>(NullNavigationTreePostProcessor.Instance);
}
}
}

74
modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs

@ -18,6 +18,8 @@ namespace Volo.Docs.Documents
{
public class DocumentAppService : DocsAppServiceBase, IDocumentAppService
{
public INavigationTreePostProcessor NavigationTreePostProcessor { get; set; }
private readonly IProjectRepository _projectRepository;
private readonly IDocumentRepository _documentRepository;
private readonly IDocumentSourceFactory _documentStoreFactory;
@ -54,6 +56,8 @@ namespace Volo.Docs.Documents
_cacheTimeout = GetCacheTimeout();
_documentResourceAbsoluteExpiration = GetDocumentResourceAbsoluteExpirationTimeout();
_documentResourceSlidingExpiration = GetDocumentResourceSlidingExpirationTimeout();
NavigationTreePostProcessor = NullNavigationTreePostProcessor.Instance;
}
public virtual async Task<DocumentWithDetailsDto> GetAsync(GetDocumentInput input)
@ -91,9 +95,11 @@ namespace Volo.Docs.Documents
input.Version
);
if (!JsonConvertExtensions.TryDeserializeObject<NavigationNode>(navigationDocument.Content, out var navigationNode))
if (!JsonConvertExtensions.TryDeserializeObject<NavigationNode>(navigationDocument.Content,
out var navigationNode))
{
throw new UserFriendlyException($"Cannot validate navigation file '{project.NavigationDocumentName}' for the project {project.Name}.");
throw new UserFriendlyException(
$"Cannot validate navigation file '{project.NavigationDocumentName}' for the project {project.Name}.");
}
var leafs = navigationNode.Items.GetAllNodes(x => x.Items)
@ -102,7 +108,9 @@ namespace Volo.Docs.Documents
foreach (var leaf in leafs)
{
var cacheKey = CacheKeyGenerator.GenerateDocumentUpdateInfoCacheKey(project, leaf.Path, input.LanguageCode, input.Version);
var cacheKey =
CacheKeyGenerator.GenerateDocumentUpdateInfoCacheKey(project, leaf.Path, input.LanguageCode,
input.Version);
var documentUpdateInfo = await DocumentUpdateCache.GetAsync(cacheKey);
if (documentUpdateInfo != null)
{
@ -112,13 +120,22 @@ namespace Volo.Docs.Documents
}
}
await NavigationTreePostProcessor.ProcessAsync(
new NavigationTreePostProcessorContext(
navigationDocument,
navigationNode
)
);
return navigationNode;
}
public async Task<DocumentResourceDto> GetResourceAsync(GetDocumentResourceInput input)
{
var project = await _projectRepository.GetAsync(input.ProjectId);
var cacheKey = CacheKeyGenerator.GenerateDocumentResourceCacheKey(project, input.Name, input.LanguageCode, input.Version);
var cacheKey =
CacheKeyGenerator.GenerateDocumentResourceCacheKey(project, input.Name, input.LanguageCode,
input.Version);
input.Version = string.IsNullOrWhiteSpace(input.Version) ? project.LatestVersionBranchName : input.Version;
async Task<DocumentResource> GetResourceAsync()
@ -133,25 +150,26 @@ namespace Volo.Docs.Documents
}
return ObjectMapper.Map<DocumentResource, DocumentResourceDto>(
await ResourceCache.GetOrAddAsync(
cacheKey,
GetResourceAsync,
() => new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = _documentResourceAbsoluteExpiration,
SlidingExpiration = _documentResourceSlidingExpiration
}
)
);
await ResourceCache.GetOrAddAsync(
cacheKey,
GetResourceAsync,
() => new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = _documentResourceAbsoluteExpiration,
SlidingExpiration = _documentResourceSlidingExpiration
}
)
);
}
public async Task<List<DocumentSearchOutput>> SearchAsync(DocumentSearchInput input)
{
var project = await _projectRepository.GetAsync(input.ProjectId);
var esDocs = await _documentFullSearch.SearchAsync(input.Context, project.Id, input.LanguageCode, input.Version);
var esDocs =
await _documentFullSearch.SearchAsync(input.Context, project.Id, input.LanguageCode, input.Version);
return esDocs.Select(esDoc => new DocumentSearchOutput//TODO: auto map
return esDocs.Select(esDoc => new DocumentSearchOutput //TODO: auto map
{
Name = esDoc.Name,
FileName = esDoc.FileName,
@ -184,9 +202,11 @@ namespace Volo.Docs.Documents
input.Version
);
if (!JsonConvertExtensions.TryDeserializeObject<DocumentParametersDto>(document.Content, out var documentParameters))
if (!JsonConvertExtensions.TryDeserializeObject<DocumentParametersDto>(document.Content,
out var documentParameters))
{
throw new UserFriendlyException($"Cannot validate document parameters file '{project.ParametersDocumentName}' for the project {project.Name}.");
throw new UserFriendlyException(
$"Cannot validate document parameters file '{project.ParametersDocumentName}' for the project {project.Name}.");
}
return documentParameters;
@ -225,7 +245,8 @@ namespace Volo.Docs.Documents
return await GetDocumentAsync(documentName, project, languageCode, version, document);
}
var cacheKey = CacheKeyGenerator.GenerateDocumentUpdateInfoCacheKey(project, document.Name, document.LanguageCode, document.Version);
var cacheKey = CacheKeyGenerator.GenerateDocumentUpdateInfoCacheKey(project, document.Name,
document.LanguageCode, document.Version);
await DocumentUpdateCache.SetAsync(cacheKey, new DocumentUpdateInfo
{
Name = document.Name,
@ -241,23 +262,28 @@ namespace Volo.Docs.Documents
{
var documentDto = ObjectMapper.Map<Document, DocumentWithDetailsDto>(document);
documentDto.Project = ObjectMapper.Map<Project, ProjectDto>(project);
documentDto.Contributors = ObjectMapper.Map<List<DocumentContributor>, List<DocumentContributorDto>>(document.Contributors);
documentDto.Contributors =
ObjectMapper.Map<List<DocumentContributor>, List<DocumentContributorDto>>(document.Contributors);
return documentDto;
}
private async Task<DocumentWithDetailsDto> GetDocumentAsync(string documentName, Project project, string languageCode, string version, Document oldDocument = null)
private async Task<DocumentWithDetailsDto> GetDocumentAsync(string documentName, Project project,
string languageCode, string version, Document oldDocument = null)
{
Logger.LogInformation($"Not found in the cache. Requesting {documentName} from the source...");
var source = _documentStoreFactory.Create(project.DocumentStoreType);
var sourceDocument = await source.GetDocumentAsync(project, documentName, languageCode, version, oldDocument?.LastSignificantUpdateTime);
var sourceDocument = await source.GetDocumentAsync(project, documentName, languageCode, version,
oldDocument?.LastSignificantUpdateTime);
await _documentRepository.DeleteAsync(project.Id, sourceDocument.Name, sourceDocument.LanguageCode, sourceDocument.Version);
await _documentRepository.DeleteAsync(project.Id, sourceDocument.Name, sourceDocument.LanguageCode,
sourceDocument.Version);
await _documentRepository.InsertAsync(sourceDocument, true);
Logger.LogInformation($"Document retrieved: {documentName}");
var cacheKey = CacheKeyGenerator.GenerateDocumentUpdateInfoCacheKey(project, sourceDocument.Name, sourceDocument.LanguageCode, sourceDocument.Version);
var cacheKey = CacheKeyGenerator.GenerateDocumentUpdateInfoCacheKey(project, sourceDocument.Name,
sourceDocument.LanguageCode, sourceDocument.Version);
await DocumentUpdateCache.SetAsync(cacheKey, new DocumentUpdateInfo
{
Name = sourceDocument.Name,

9
modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/INavigationTreePostProcessor.cs

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Volo.Docs.Documents
{
public interface INavigationTreePostProcessor
{
Task ProcessAsync(NavigationTreePostProcessorContext context);
}
}

14
modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/NavigationTreePostProcessorContext.cs

@ -0,0 +1,14 @@
namespace Volo.Docs.Documents
{
public class NavigationTreePostProcessorContext
{
public DocumentWithDetailsDto Document { get; }
public NavigationNode RootNode { get; }
public NavigationTreePostProcessorContext(DocumentWithDetailsDto document, NavigationNode rootNode)
{
Document = document;
RootNode = rootNode;
}
}
}

20
modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/NullNavigationTreePostProcessor.cs

@ -0,0 +1,20 @@
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace Volo.Docs.Documents
{
public sealed class NullNavigationTreePostProcessor : INavigationTreePostProcessor
{
public static NullNavigationTreePostProcessor Instance { get; } = new NullNavigationTreePostProcessor();
private NullNavigationTreePostProcessor()
{
}
public Task ProcessAsync(NavigationTreePostProcessorContext context)
{
return Task.CompletedTask;
}
}
}

5
modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hant.json

@ -63,6 +63,8 @@
"Identity.PasswordConfirmationFailed": "密碼或確認密碼不一致.",
"Identity.StaticRoleRenamingErrorMessage": "無法重命名靜態角色.",
"Identity.StaticRoleDeletionErrorMessage": "無法刪除靜態角色.",
"Identity.OrganizationUnit.DuplicateDisplayNameWarning": "組織內單位內已包含名稱 {0}.同一層級組織,兩個單位不能有相同名稱",
"Identity.OrganizationUnit.MaxUserMembershipCount": "允許一個使用者至多可隸屬在幾個組織單位",
"Volo.Abp.Identity:010001": "您無法刪除自己的帳號!",
"Permission:IdentityManagement": "身份識別管理",
"Permission:RoleManagement": "角色管理",
@ -99,6 +101,7 @@
"Description:Abp.Identity.SignIn.EnablePhoneNumberConfirmation": "使用者手機號碼是否需要驗證.",
"Description:Abp.Identity.SignIn.RequireConfirmedPhoneNumber": "登入時是否需要驗證手機號碼.",
"Description:Abp.Identity.User.IsUserNameUpdateEnabled": "是否允許使用者更新使用者名稱.",
"Description:Abp.Identity.User.IsEmailUpdateEnabled": "是否允許使用者更新電子信箱."
"Description:Abp.Identity.User.IsEmailUpdateEnabled": "是否允許使用者更新電子信箱.",
"Volo.Abp.Identity:010002": "一個使用者不能設置超過 {MaxUserMembershipCount} 個組織單位下"
}
}

25
modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs

@ -22,7 +22,6 @@ namespace Volo.Abp.Identity
protected IIdentityRoleRepository RoleRepository { get; }
protected IIdentityUserRepository UserRepository { get; }
protected IOrganizationUnitRepository OrganizationUnitRepository { get; }
protected IIdentityUserRepository IdentityUserRepository { get; }
protected ISettingProvider SettingProvider { get; }
protected ICancellationTokenProvider CancellationTokenProvider { get; }
@ -42,7 +41,6 @@ namespace Volo.Abp.Identity
ILogger<IdentityUserManager> logger,
ICancellationTokenProvider cancellationTokenProvider,
IOrganizationUnitRepository organizationUnitRepository,
IIdentityUserRepository identityUserRepository,
ISettingProvider settingProvider)
: base(
store,
@ -56,7 +54,6 @@ namespace Volo.Abp.Identity
logger)
{
OrganizationUnitRepository = organizationUnitRepository;
IdentityUserRepository = identityUserRepository;
SettingProvider = settingProvider;
RoleRepository = roleRepository;
UserRepository = userRepository;
@ -98,27 +95,27 @@ namespace Volo.Abp.Identity
public virtual async Task<bool> IsInOrganizationUnitAsync(Guid userId, Guid ouId)
{
var user = await IdentityUserRepository.GetAsync(userId, cancellationToken: CancellationToken);
var user = await UserRepository.GetAsync(userId, cancellationToken: CancellationToken);
return user.IsInOrganizationUnit(ouId);
}
public virtual async Task<bool> IsInOrganizationUnitAsync(IdentityUser user, OrganizationUnit ou)
{
await IdentityUserRepository.EnsureCollectionLoadedAsync(user, u => u.OrganizationUnits, CancellationTokenProvider.Token);
await UserRepository.EnsureCollectionLoadedAsync(user, u => u.OrganizationUnits, CancellationTokenProvider.Token);
return user.IsInOrganizationUnit(ou.Id);
}
public virtual async Task AddToOrganizationUnitAsync(Guid userId, Guid ouId)
{
await AddToOrganizationUnitAsync(
await IdentityUserRepository.GetAsync(userId, cancellationToken: CancellationToken),
await UserRepository.GetAsync(userId, cancellationToken: CancellationToken),
await OrganizationUnitRepository.GetAsync(ouId, cancellationToken: CancellationToken)
);
}
public virtual async Task AddToOrganizationUnitAsync(IdentityUser user, OrganizationUnit ou)
{
await IdentityUserRepository.EnsureCollectionLoadedAsync(user, u => u.OrganizationUnits, CancellationTokenProvider.Token);
await UserRepository.EnsureCollectionLoadedAsync(user, u => u.OrganizationUnits, CancellationTokenProvider.Token);
if (user.OrganizationUnits.Any(cou => cou.OrganizationUnitId == ou.Id))
{
@ -132,13 +129,13 @@ namespace Volo.Abp.Identity
public virtual async Task RemoveFromOrganizationUnitAsync(Guid userId, Guid ouId)
{
var user = await IdentityUserRepository.GetAsync(userId, cancellationToken: CancellationToken);
var user = await UserRepository.GetAsync(userId, cancellationToken: CancellationToken);
user.RemoveOrganizationUnit(ouId);
}
public virtual async Task RemoveFromOrganizationUnitAsync(IdentityUser user, OrganizationUnit ou)
{
await IdentityUserRepository.EnsureCollectionLoadedAsync(user, u => u.OrganizationUnits, CancellationTokenProvider.Token);
await UserRepository.EnsureCollectionLoadedAsync(user, u => u.OrganizationUnits, CancellationTokenProvider.Token);
user.RemoveOrganizationUnit(ou.Id);
}
@ -146,7 +143,7 @@ namespace Volo.Abp.Identity
public virtual async Task SetOrganizationUnitsAsync(Guid userId, params Guid[] organizationUnitIds)
{
await SetOrganizationUnitsAsync(
await IdentityUserRepository.GetAsync(userId, cancellationToken: CancellationToken),
await UserRepository.GetAsync(userId, cancellationToken: CancellationToken),
organizationUnitIds
);
}
@ -158,7 +155,7 @@ namespace Volo.Abp.Identity
await CheckMaxUserOrganizationUnitMembershipCountAsync(organizationUnitIds.Length);
await IdentityUserRepository.EnsureCollectionLoadedAsync(user, u => u.OrganizationUnits, CancellationTokenProvider.Token);
await UserRepository.EnsureCollectionLoadedAsync(user, u => u.OrganizationUnits, CancellationTokenProvider.Token);
//Remove from removed OUs
foreach (var ouId in user.OrganizationUnits.Select(uou => uou.OrganizationUnitId).ToArray())
@ -192,7 +189,7 @@ namespace Volo.Abp.Identity
[UnitOfWork]
public virtual async Task<List<OrganizationUnit>> GetOrganizationUnitsAsync(IdentityUser user, bool includeDetails = false)
{
await IdentityUserRepository.EnsureCollectionLoadedAsync(user, u => u.OrganizationUnits, CancellationTokenProvider.Token);
await UserRepository.EnsureCollectionLoadedAsync(user, u => u.OrganizationUnits, CancellationTokenProvider.Token);
return await OrganizationUnitRepository.GetListAsync(
user.OrganizationUnits.Select(t => t.OrganizationUnitId),
@ -208,12 +205,12 @@ namespace Volo.Abp.Identity
{
if (includeChildren)
{
return await IdentityUserRepository
return await UserRepository
.GetUsersInOrganizationUnitWithChildrenAsync(organizationUnit.Code, CancellationToken);
}
else
{
return await IdentityUserRepository
return await UserRepository
.GetUsersInOrganizationUnitAsync(organizationUnit.Id, CancellationToken);
}
}

6
modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs

@ -139,7 +139,8 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
u.UserName.Contains(filter) ||
u.Email.Contains(filter) ||
(u.Name != null && u.Name.Contains(filter)) ||
(u.Surname != null && u.Surname.Contains(filter))
(u.Surname != null && u.Surname.Contains(filter)) ||
(u.PhoneNumber != null && u.PhoneNumber.Contains(filter))
)
.OrderBy(sorting ?? nameof(IdentityUser.UserName))
.PageBy(skipCount, maxResultCount)
@ -184,7 +185,8 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
u.UserName.Contains(filter) ||
u.Email.Contains(filter) ||
(u.Name != null && u.Name.Contains(filter)) ||
(u.Surname != null && u.Surname.Contains(filter))
(u.Surname != null && u.Surname.Contains(filter)) ||
(u.PhoneNumber != null && u.PhoneNumber.Contains(filter))
)
.LongCountAsync(GetCancellationToken(cancellationToken));
}

6
modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs

@ -134,7 +134,8 @@ namespace Volo.Abp.Identity.MongoDB
u.UserName.Contains(filter) ||
u.Email.Contains(filter) ||
(u.Name != null && u.Name.Contains(filter)) ||
(u.Surname != null && u.Surname.Contains(filter))
(u.Surname != null && u.Surname.Contains(filter)) ||
(u.PhoneNumber != null && u.PhoneNumber.Contains(filter))
)
.OrderBy(sorting ?? nameof(IdentityUser.UserName))
.As<IMongoQueryable<IdentityUser>>()
@ -185,7 +186,8 @@ namespace Volo.Abp.Identity.MongoDB
u.UserName.Contains(filter) ||
u.Email.Contains(filter) ||
(u.Name != null && u.Name.Contains(filter)) ||
(u.Surname != null && u.Surname.Contains(filter))
(u.Surname != null && u.Surname.Contains(filter)) ||
(u.PhoneNumber != null && u.PhoneNumber.Contains(filter))
)
.LongCountAsync(GetCancellationToken(cancellationToken));
}

28
npm/ng-packs/package.json

@ -22,20 +22,20 @@
"generate:changelog": "conventional-changelog -p angular -i CHANGELOG.md -s"
},
"devDependencies": {
"@abp/ng.account": "~2.8.0",
"@abp/ng.account.config": "~2.8.0",
"@abp/ng.core": "~2.8.0",
"@abp/ng.feature-management": "~2.8.0",
"@abp/ng.identity": "~2.8.0",
"@abp/ng.identity.config": "~2.8.0",
"@abp/ng.permission-management": "~2.8.0",
"@abp/ng.setting-management": "~2.8.0",
"@abp/ng.setting-management.config": "~2.8.0",
"@abp/ng.tenant-management": "~2.8.0",
"@abp/ng.tenant-management.config": "~2.8.0",
"@abp/ng.theme.basic": "~2.8.0",
"@abp/ng.theme.shared": "~2.8.0",
"@abp/utils": "^2.7.0",
"@abp/ng.account": "~2.9.0",
"@abp/ng.account.config": "~2.9.0",
"@abp/ng.core": "~2.9.0",
"@abp/ng.feature-management": "~2.9.0",
"@abp/ng.identity": "~2.9.0",
"@abp/ng.identity.config": "~2.9.0",
"@abp/ng.permission-management": "~2.9.0",
"@abp/ng.setting-management": "~2.9.0",
"@abp/ng.setting-management.config": "~2.9.0",
"@abp/ng.tenant-management": "~2.9.0",
"@abp/ng.tenant-management.config": "~2.9.0",
"@abp/ng.theme.basic": "~2.9.0",
"@abp/ng.theme.shared": "~2.9.0",
"@abp/utils": "^2.9.0",
"@angular-builders/jest": "^8.2.0",
"@angular-devkit/build-angular": "~0.803.21",
"@angular-devkit/build-ng-packagr": "~0.803.21",

2
npm/ng-packs/packages/theme-shared/package.json

@ -1,6 +1,6 @@
{
"name": "@abp/ng.theme.shared",
"version": "2.9.0",
"version": "2.9.1",
"homepage": "https://abp.io",
"repository": {
"type": "git",

28
npm/ng-packs/packages/theme-shared/src/lib/handlers/lazy-style.handler.ts

@ -18,6 +18,8 @@ export class LazyStyleHandler {
private styles: string[];
private _dir: LocaleDirection = 'ltr';
readonly loaded = new Map<string, HTMLLinkElement>();
set dir(dir: LocaleDirection) {
if (dir === this._dir) return;
@ -36,9 +38,17 @@ export class LazyStyleHandler {
this.listenToLanguageChanges(injector);
}
private getHrefFromLink(link: HTMLLinkElement | null): string {
if (!link) return '';
const a = document.createElement('a');
a.href = link.href;
return a.pathname.replace(/^\//, '');
}
private getLoadedBootstrap(): LoadedStyle {
const href = createLazyStyleHref(BOOTSTRAP, this.dir);
const selector = `[href$="${href}"]`;
const selector = `[href*="${href.replace(/\.css$/, '')}"]`;
const link = document.querySelector<HTMLLinkElement>(selector);
return { href, link };
}
@ -75,9 +85,19 @@ export class LazyStyleHandler {
this.styles.forEach(style => {
const oldHref = createLazyStyleHref(style, this.dir);
const newHref = createLazyStyleHref(style, dir);
const strategy = LOADING_STRATEGY.PrependAnonymousStyleToHead(newHref);
this.lazyLoad.load(strategy).subscribe(() => this.lazyLoad.remove(oldHref));
const link = this.loaded.get(newHref);
const href = this.getHrefFromLink(link) || newHref;
const strategy = LOADING_STRATEGY.PrependAnonymousStyleToHead(href);
this.lazyLoad.load(strategy).subscribe(() => {
const oldLink = this.lazyLoad.loaded.get(oldHref) as HTMLLinkElement;
this.loaded.delete(newHref);
this.loaded.set(oldHref, oldLink);
const newLink = this.lazyLoad.loaded.get(href) as HTMLLinkElement;
this.lazyLoad.loaded.delete(href);
this.lazyLoad.loaded.set(newHref, newLink);
this.lazyLoad.remove(oldHref);
});
});
}
}

139
npm/ng-packs/yarn.lock

@ -2,26 +2,26 @@
# yarn lockfile v1
"@abp/ng.account.config@~2.8.0":
version "2.8.0"
resolved "https://registry.yarnpkg.com/@abp/ng.account.config/-/ng.account.config-2.8.0.tgz#c641a2d07c3a4ffc9fd4638b7000918791fe01d1"
integrity sha512-1MDOdrvaFuv5jW95bOcstfV2buN5PFwYfW6PSxP6DIqrpVLvDvyvtkX+VptVL3q/grufiI8g1Yed02hERAzo6g==
"@abp/ng.account.config@~2.9.0":
version "2.9.0"
resolved "https://registry.yarnpkg.com/@abp/ng.account.config/-/ng.account.config-2.9.0.tgz#fdb930db942def047823a6e891ebeeae7f8a19a4"
integrity sha512-lC/wpb4HeVf6eJiZjKJKtLEBQhR2x364Cs51cIHA8vEs8bJjZX/oodLYeItT3SuDii7MkREiv9uc12pMLK96IA==
dependencies:
tslib "^1.9.0"
"@abp/ng.account@~2.8.0":
version "2.8.0"
resolved "https://registry.yarnpkg.com/@abp/ng.account/-/ng.account-2.8.0.tgz#ee9cf3570d4f69264756e472043d217a5f8fa46f"
integrity sha512-E4BMEwSWnvYm5A82+U+slVR4ecccq1Z3wJb5rmm1+ExQBbaadM2qrn8rrak0n+rSI0qcQkDncDIsbp7fb7AYJA==
"@abp/ng.account@~2.9.0":
version "2.9.0"
resolved "https://registry.yarnpkg.com/@abp/ng.account/-/ng.account-2.9.0.tgz#a863b0bdf9f7027738e4da40a8bcce867fef52bc"
integrity sha512-NnSKdCMbx87lIxyB8oYGYAAk4jYmmqIUElfNIC5D5pI76ekQrBVFD2ebimDgOyHl7oMS/k6byHNG3RhXln6pXg==
dependencies:
"@abp/ng.account.config" "~2.8.0"
"@abp/ng.theme.shared" "~2.8.0"
"@abp/ng.account.config" "~2.9.0"
"@abp/ng.theme.shared" "~2.9.0"
tslib "^1.9.0"
"@abp/ng.core@~2.8.0":
version "2.8.0"
resolved "https://registry.yarnpkg.com/@abp/ng.core/-/ng.core-2.8.0.tgz#1b4940a7d37f56eb7ec3f99275d32d43f68b600d"
integrity sha512-LxUPm+gQGake9/CMvx2ZukcAsDExziTNqTf5vQLWN2Ggx85wPbajqJc0zP08i8ST2LRP/3DCjRS/Cg6QVp+I2A==
"@abp/ng.core@~2.9.0":
version "2.9.0"
resolved "https://registry.yarnpkg.com/@abp/ng.core/-/ng.core-2.9.0.tgz#7fd64163355ebb9e43b63ff3fbbc7f4d4470033f"
integrity sha512-hZ7u5imE7PCzinvdeFQA4kaNAiUaY+CpuiFTAHmxVNzNtv7SNqLXw1VmltklFJTj98ZXQOWCSiliBWZSYwKGPA==
dependencies:
"@abp/utils" "^2.7.0"
"@angular/localize" "~9.1.0"
@ -35,86 +35,86 @@
ts-toolbelt "^6.3.6"
tslib "^1.9.0"
"@abp/ng.feature-management@~2.8.0":
version "2.8.0"
resolved "https://registry.yarnpkg.com/@abp/ng.feature-management/-/ng.feature-management-2.8.0.tgz#8fed791071142f2980ccbb60b362c042c6fcdbea"
integrity sha512-4FHR43vf0AlQqbIZjRVBYZPORPe8sW0DXraChuuhskMOliDqqXVSk5rx+72qnxvUZhS0eo40nd5Ns/rA34RMNw==
"@abp/ng.feature-management@~2.9.0":
version "2.9.0"
resolved "https://registry.yarnpkg.com/@abp/ng.feature-management/-/ng.feature-management-2.9.0.tgz#a3d1fdb7df081cb7d56819107c3d4db5628a6191"
integrity sha512-tQDq3I1IvrJA4VarY3nqdR0n9/s7bcZidAFPb2VdnqVb2Ri/iNThu8oYQ7zx9lhT+UarCvJYmGwAkNAzd+9NQQ==
dependencies:
"@abp/ng.theme.shared" "~2.8.0"
"@abp/ng.theme.shared" "~2.9.0"
tslib "^1.9.0"
"@abp/ng.identity.config@~2.8.0":
version "2.8.0"
resolved "https://registry.yarnpkg.com/@abp/ng.identity.config/-/ng.identity.config-2.8.0.tgz#32372ddf784c2a92ff979435faa255b6c277db0a"
integrity sha512-8SGXu435KdY0TsFCupEKmlI37aPgGGANglv16j90013BTDQVGSPpdTGawN1I0En3r6ODZKHP3rNble+fFiCJ+A==
"@abp/ng.identity.config@~2.9.0":
version "2.9.0"
resolved "https://registry.yarnpkg.com/@abp/ng.identity.config/-/ng.identity.config-2.9.0.tgz#c8c5a30817b8d953d91288f67325e2ddb3edb922"
integrity sha512-aHi/7PvSomHUNWOk2sWw/W/IqJK2icFHsqtrsS0Op9HtVK6ORW5DSroWTU7CrY39Tnv93sRPBW+hTq0pA92yVw==
dependencies:
tslib "^1.9.0"
"@abp/ng.identity@~2.8.0":
version "2.8.0"
resolved "https://registry.yarnpkg.com/@abp/ng.identity/-/ng.identity-2.8.0.tgz#b77cfe08b72f69fc9f8c2d74dd8cf1f85358c4c9"
integrity sha512-LvzTfyrzLAcHeYivDHZJkSLYk7ZtTKp218eBjKuav2HSrvKUlBHKL50croldDguFNnq1uVRCYIBS7Vpj8lH1Fg==
"@abp/ng.identity@~2.9.0":
version "2.9.0"
resolved "https://registry.yarnpkg.com/@abp/ng.identity/-/ng.identity-2.9.0.tgz#d0343122cf47191f49d35b291ad548f07a47630e"
integrity sha512-o/BMi3MfF+h7bFS12tfaqldFGS/dDUKSGU89ufygUKEyAFFpTZTVOc5aI+8/FxVEx5ag5ibSeS943BHhCp0WEg==
dependencies:
"@abp/ng.identity.config" "~2.8.0"
"@abp/ng.permission-management" "~2.8.0"
"@abp/ng.theme.shared" "~2.8.0"
"@abp/ng.identity.config" "~2.9.0"
"@abp/ng.permission-management" "~2.9.0"
"@abp/ng.theme.shared" "~2.9.0"
tslib "^1.9.0"
"@abp/ng.permission-management@~2.8.0":
version "2.8.0"
resolved "https://registry.yarnpkg.com/@abp/ng.permission-management/-/ng.permission-management-2.8.0.tgz#832c71b2f91392493d9390751227dcede5406612"
integrity sha512-T4DMTQe0YA2JtKA5Avb41vBVB1rjjIDZO2ykCq1FqSIbXKHLUliZ1CSnOWMWqOzc7LfsdT0qyReQtoRGGiTFGg==
"@abp/ng.permission-management@~2.9.0":
version "2.9.0"
resolved "https://registry.yarnpkg.com/@abp/ng.permission-management/-/ng.permission-management-2.9.0.tgz#9ddbc23ea4948c57ff30286d32b7216fbfeb059c"
integrity sha512-fRENj1l0ZsUK/pEutY+i0G+Hwuel5K6PKVpBxUYmZIO1KQ2KvMA9Hj/sDsxI/zpM0GUIha7TksMFr5NCwKcLjg==
dependencies:
"@abp/ng.theme.shared" "~2.8.0"
"@abp/ng.theme.shared" "~2.9.0"
tslib "^1.9.0"
"@abp/ng.setting-management.config@~2.8.0":
version "2.8.0"
resolved "https://registry.yarnpkg.com/@abp/ng.setting-management.config/-/ng.setting-management.config-2.8.0.tgz#7ce2ad4f428bd3d66810c3c868702b72001b79e5"
integrity sha512-EVtgpCa3ApYZhPpctex3KwLBuJ14jxVMie4xUEg+txxKIhcV2aTAS8pFT4D9erJvW5wAXMNY/h34eZo0eFhS/g==
"@abp/ng.setting-management.config@~2.9.0":
version "2.9.0"
resolved "https://registry.yarnpkg.com/@abp/ng.setting-management.config/-/ng.setting-management.config-2.9.0.tgz#fce1a8a264f201961ba8a1daf92a4ca74cf24c4a"
integrity sha512-9BUGmCQFYZ8TZhN6enenHgu/Cenpey95pEHsXXqdBdNy2vKQmcOuOMMRZtB3H5OxqqXMAzVCxbGQP3loihgx8g==
dependencies:
tslib "^1.9.0"
"@abp/ng.setting-management@~2.8.0":
version "2.8.0"
resolved "https://registry.yarnpkg.com/@abp/ng.setting-management/-/ng.setting-management-2.8.0.tgz#6676e5b0aaf9c5c80bc2d630611dd1b635948637"
integrity sha512-CBlP4h/1/zgS7r2iKQlV8aDvnlmGH+4uGlNM8c1Usprisap8A9yfLc56UOkeU26os/dwitxwDFV6yJDPKiPa0g==
"@abp/ng.setting-management@~2.9.0":
version "2.9.0"
resolved "https://registry.yarnpkg.com/@abp/ng.setting-management/-/ng.setting-management-2.9.0.tgz#43fad32df23c4c31e49cbc86b45bdb071720392b"
integrity sha512-4vZVMPMxFbM5h2IKSQE23VWQledKI0EDIpkzNDw/zlhOadWOJ9z5jmHT7DOf1qSETJ5cNJsHhZgDwMyzsOzjeA==
dependencies:
"@abp/ng.setting-management.config" "~2.8.0"
"@abp/ng.theme.shared" "~2.8.0"
"@abp/ng.setting-management.config" "~2.9.0"
"@abp/ng.theme.shared" "~2.9.0"
tslib "^1.9.0"
"@abp/ng.tenant-management.config@~2.8.0":
version "2.8.0"
resolved "https://registry.yarnpkg.com/@abp/ng.tenant-management.config/-/ng.tenant-management.config-2.8.0.tgz#9e09101f07ee91005f41a79d87028c091855b48c"
integrity sha512-Z/9kzlHqt6bsmLqoSTPt3MDl11wlxQfWcDty7/zI4alx27omCiXUp+pEvw3TYTg9yhyIf6mehfQkknwzrxvYew==
"@abp/ng.tenant-management.config@~2.9.0":
version "2.9.0"
resolved "https://registry.yarnpkg.com/@abp/ng.tenant-management.config/-/ng.tenant-management.config-2.9.0.tgz#79e0ee917e5006f893a83b27d2af2c8e1fdeaf41"
integrity sha512-yVF+8NEE+6EzSedhxt6Zf89NhOHVysSyu62sLNOzgmqngKQ/oxs08tAEb7vcCkHNYnBisOhQvTg0aqdP7Ax2Jw==
dependencies:
tslib "^1.9.0"
"@abp/ng.tenant-management@~2.8.0":
version "2.8.0"
resolved "https://registry.yarnpkg.com/@abp/ng.tenant-management/-/ng.tenant-management-2.8.0.tgz#44b51b2d76d7c2a118a3e2ff2c871751bb05a75d"
integrity sha512-pm8uhmalEhLMHx1mdj+Fua8wt+V7brw6s4mi7wnISuqKcfIB77hUI4MvTTZhS0GfS4T+EL81V2Yt0Y/DRTX8xQ==
"@abp/ng.tenant-management@~2.9.0":
version "2.9.0"
resolved "https://registry.yarnpkg.com/@abp/ng.tenant-management/-/ng.tenant-management-2.9.0.tgz#5ec9c092d95051e6c5077ed55999e0f3cf254765"
integrity sha512-Y7hP1akqo0IqcaTiZKXjUtUZtCnsROZ/pps/BzisBukIgtYvdRsqc/UeQ9YC32lL+O0xwQPyjcJ4/mir0KPS4Q==
dependencies:
"@abp/ng.feature-management" "~2.8.0"
"@abp/ng.tenant-management.config" "~2.8.0"
"@abp/ng.theme.shared" "~2.8.0"
"@abp/ng.feature-management" "~2.9.0"
"@abp/ng.tenant-management.config" "~2.9.0"
"@abp/ng.theme.shared" "~2.9.0"
tslib "^1.9.0"
"@abp/ng.theme.basic@~2.8.0":
version "2.8.0"
resolved "https://registry.yarnpkg.com/@abp/ng.theme.basic/-/ng.theme.basic-2.8.0.tgz#9ecd23e75b264a851b8665e3e08d99306a44ec7b"
integrity sha512-5o3+Vbz9bA2IEe2IcDbUFHsfxMEcfe8rs2/A2o6Z4BQaqus+JtrQhxd1BExXb39NO8gdUBQ8fkyZVkOuyL7e6g==
"@abp/ng.theme.basic@~2.9.0":
version "2.9.0"
resolved "https://registry.yarnpkg.com/@abp/ng.theme.basic/-/ng.theme.basic-2.9.0.tgz#aa0351de9ec8237136f2f2ef014077cc330cefcf"
integrity sha512-TS3Ikeg+HPdBLXbzNcKbA2Ewi2zUbvCkOiF2aZxYCMrKOXDax6d0sN8m64X+vWuL4+cPtzcNg5bWsiDtUYRE2A==
dependencies:
"@abp/ng.theme.shared" "~2.8.0"
"@abp/ng.theme.shared" "~2.9.0"
tslib "^1.9.0"
"@abp/ng.theme.shared@~2.8.0":
version "2.8.0"
resolved "https://registry.yarnpkg.com/@abp/ng.theme.shared/-/ng.theme.shared-2.8.0.tgz#feb4ce0908b99e72fb230b1830bef6a6321cc4bd"
integrity sha512-0iVpvpLcpwMBLQk2VIKWxz6FktpJIbgxpgT+ypEUy6zDmaTT3TZuTeApleGJVLbEz2jkdEj2ApZH2asNaT1bPA==
"@abp/ng.theme.shared@~2.9.0":
version "2.9.0"
resolved "https://registry.yarnpkg.com/@abp/ng.theme.shared/-/ng.theme.shared-2.9.0.tgz#aaf97cf94ff798b3695cbb1b0dec33d20996fbda"
integrity sha512-04xlt3aKiDVyE+6NpxA0KGHGuupJWhXTjPWwlzVtfNiPqYjx0rG2FbTGgNWMW4K1qTlRRVjPA5a/MDhZtZHZIw==
dependencies:
"@abp/ng.core" "~2.8.0"
"@abp/ng.core" "~2.9.0"
"@fortawesome/fontawesome-free" "^5.12.1"
"@ng-bootstrap/ng-bootstrap" "^5.3.0"
"@ngx-validate/core" "^0.0.7"
@ -129,6 +129,13 @@
dependencies:
just-compare "^1.3.0"
"@abp/utils@^2.9.0":
version "2.9.0"
resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-2.9.0.tgz#8a66b7e1d422eae5898d32af7810d6bd625276f2"
integrity sha512-0gvWUH3jT7YE4ym2C45uKDzHs/oOdMW5lPq9ncCZ58wMxTrgPvxXNuMmRW0P259OcXYiRwmsNtb09/WlTQQbKA==
dependencies:
just-compare "^1.3.0"
"@angular-builders/jest@^8.2.0":
version "8.3.2"
resolved "https://registry.yarnpkg.com/@angular-builders/jest/-/jest-8.3.2.tgz#669c350c9df2e3afc9ae048c5067759c36113728"

2
samples/MicroserviceDemo/gateways/PublicWebSiteGateway.Host/PublicWebSiteGateway.Host.csproj

@ -24,7 +24,7 @@
<ItemGroup>
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AspNetCore.MultiTenancy\Volo.Abp.AspNetCore.MultiTenancy.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy\Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.EntityFrameworkCore.SqlServer\Volo.Abp.EntityFrameworkCore.SqlServer.csproj" />
<ProjectReference Include="..\..\..\..\modules\blogging\src\Volo.Blogging.HttpApi\Volo.Blogging.HttpApi.csproj" />
<ProjectReference Include="..\..\..\..\modules\permission-management\src\Volo.Abp.PermissionManagement.EntityFrameworkCore\Volo.Abp.PermissionManagement.EntityFrameworkCore.csproj" />

3
samples/MicroserviceDemo/gateways/PublicWebSiteGateway.Host/PublicWebSiteGatewayHostModule.cs

@ -13,6 +13,7 @@ using MsDemo.Shared;
using Swashbuckle.AspNetCore.Swagger;
using Volo.Abp;
using Volo.Abp.AspNetCore.MultiTenancy;
using Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy;
using Volo.Abp.Autofac;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.SqlServer;
@ -32,7 +33,7 @@ namespace PublicWebSiteGateway.Host
typeof(AbpEntityFrameworkCoreSqlServerModule),
typeof(AbpPermissionManagementEntityFrameworkCoreModule),
typeof(AbpSettingManagementEntityFrameworkCoreModule),
typeof(AbpAspNetCoreMultiTenancyModule)
typeof(AbpAspNetCoreMvcUiMultiTenancyModule)
)]
public class PublicWebSiteGatewayHostModule : AbpModule
{

25
samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Pages/ProductManagement/ProductManagementPage.cs

@ -1,25 +0,0 @@
using Microsoft.AspNetCore.Mvc.Localization;
using Microsoft.AspNetCore.Mvc.Razor.Internal;
using ProductManagement.Localization;
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;
namespace ProductManagement.Pages.ProductManagement
{
public abstract class ProductManagementPage : AbpPage
{
[RazorInject]
public IHtmlLocalizer<ProductManagementResource> L { get; set; }
public const string DefaultTitle = "ProductManagement";
public string GetTitle(string title = null)
{
if (string.IsNullOrWhiteSpace(title))
{
return DefaultTitle;
}
return title;
}
}
}

4
samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Pages/ProductManagement/Products/Create.cshtml

@ -1,7 +1,9 @@
@page
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@inherits ProductManagement.Pages.ProductManagement.ProductManagementPage
@using Microsoft.AspNetCore.Mvc.Localization
@using ProductManagement.Localization
@model ProductManagement.Pages.ProductManagement.Products.CreateModel
@inject IHtmlLocalizer<ProductManagementResource> L
@{
Layout = null;
}

6
samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Pages/ProductManagement/Products/Edit.cshtml

@ -1,7 +1,9 @@
@page
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@inherits ProductManagement.Pages.ProductManagement.ProductManagementPage
@using Microsoft.AspNetCore.Mvc.Localization
@using ProductManagement.Localization
@model ProductManagement.Pages.ProductManagement.Products.EditModel
@inject IHtmlLocalizer<ProductManagementResource> L
@{
Layout = null;
}
@ -15,4 +17,4 @@
<abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)">
</abp-modal-footer>
</abp-modal>
</abp-dynamic-form>
</abp-dynamic-form>

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save