Browse Source

Merge branch 'dev' of https://github.com/abpframework/abp into dev

pull/4354/head
Akın Sabri Çam 6 years ago
parent
commit
70c2958356
  1. 7
      apiSpec/Microsoft_AspNetCore_Routing_AbpEndpointRouterOptions.md
  2. 23
      docs/en/API/Application-Configuration.md
  3. 3
      docs/en/API/JavaScript-API/Auth.md
  4. 4
      docs/en/Application-Services.md
  5. 2
      docs/en/AspNet-Boilerplate-Migration-Guide.md
  6. 3
      docs/en/AspNetCore/JavaScript-API/Auth.md
  7. 3
      docs/en/AspNetCore/JavaScript-API/Index.md
  8. 58
      docs/en/Blob-Storing-Azure.md
  9. 4
      docs/en/Blob-Storing-Custom-Provider.md
  10. 2
      docs/en/Blob-Storing-Database.md
  11. 3
      docs/en/Clock.md
  12. 2
      docs/en/CurrentUser.md
  13. 159
      docs/en/Data-Seeding.md
  14. 281
      docs/en/Data-Transfer-Objects.md
  15. 4
      docs/en/Domain-Driven-Design.md
  16. 8
      docs/en/Entities.md
  17. 4
      docs/en/Guid-Generation.md
  18. 3
      docs/en/Json.md
  19. 29
      docs/en/Modules/Docs.md
  20. 8
      docs/en/Modules/Virtual-File-Explorer.md
  21. 113
      docs/en/Timing.md
  22. 5
      docs/en/UI/AspNetCore/JavaScript-API/Index.md
  23. 37
      docs/en/UI/AspNetCore/Tag-Helpers/Button-groups.md
  24. 74
      docs/en/UI/AspNetCore/Tag-Helpers/Carousel.md
  25. 20
      docs/en/docs-nav.json
  26. 22
      docs/zh-Hans/API/Application-Configuration.md
  27. 2
      docs/zh-Hans/Application-Services.md
  28. 58
      docs/zh-Hans/Blob-Storing-Azure.md
  29. 177
      docs/zh-Hans/Blob-Storing-Custom-Provider.md
  30. 96
      docs/zh-Hans/Blob-Storing-Database.md
  31. 58
      docs/zh-Hans/Blob-Storing-File-System.md
  32. 306
      docs/zh-Hans/Blob-Storing.md
  33. 1
      docs/zh-Hans/CLI.md
  34. 14
      docs/zh-Hans/Connection-Strings.md
  35. 168
      docs/zh-Hans/CurrentUser.md
  36. 161
      docs/zh-Hans/Data-Seeding.md
  37. 281
      docs/zh-Hans/Data-Transfer-Objects.md
  38. 2
      docs/zh-Hans/Domain-Driven-Design.md
  39. 6
      docs/zh-Hans/Entities.md
  40. 112
      docs/zh-Hans/Guid-Generation.md
  41. 4
      docs/zh-Hans/Index.md
  42. 30
      docs/zh-Hans/Modules/Docs.md
  43. 8
      docs/zh-Hans/Modules/Virtual-File-Explorer.md
  44. 19
      docs/zh-Hans/Startup-Templates/Console.md
  45. 3
      docs/zh-Hans/Startup-Templates/Index.md
  46. 113
      docs/zh-Hans/Timing.md
  47. 4
      docs/zh-Hans/UI/Angular/Component-Replacement.md
  48. 500
      docs/zh-Hans/UI/Angular/Permission-Management-Component-Replacement.md
  49. BIN
      docs/zh-Hans/UI/Angular/images/layout-components.png
  50. BIN
      docs/zh-Hans/UI/Angular/images/logo-component.png
  51. BIN
      docs/zh-Hans/UI/Angular/images/nav-items-component.png
  52. BIN
      docs/zh-Hans/UI/Angular/images/permission-management-modal.png
  53. BIN
      docs/zh-Hans/UI/Angular/images/replaced-logo-component.png
  54. BIN
      docs/zh-Hans/UI/Angular/images/replaced-nav-items-component.png
  55. BIN
      docs/zh-Hans/UI/Angular/images/replaced-routes-component.png
  56. BIN
      docs/zh-Hans/UI/Angular/images/routes-component.png
  57. 23
      docs/zh-Hans/UI/AspNetCore/JavaScript-API/Index.md
  58. 38
      docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Badges.md
  59. 124
      docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Borders.md
  60. 25
      docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Breadcrumbs.md
  61. 37
      docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Button-groups.md
  62. 71
      docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Carousel.md
  63. 114
      docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Navs.md
  64. 59
      docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Tables.md
  65. 88
      docs/zh-Hans/docs-nav.json
  66. 14
      framework/Volo.Abp.sln
  67. 4
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Carousel/AbpCarouselTagHelperService.cs
  68. 5
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperResourceService.cs
  69. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Resources/IWebRequestResources.cs
  70. 28
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Resources/WebRequestResources.cs
  71. 24
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs
  72. 4
      framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueue.cs
  73. 3
      framework/src/Volo.Abp.BlobStoring.Azure/FodyWeavers.xml
  74. 30
      framework/src/Volo.Abp.BlobStoring.Azure/FodyWeavers.xsd
  75. 22
      framework/src/Volo.Abp.BlobStoring.Azure/Volo.Abp.BlobStoring.Azure.csproj
  76. 10
      framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureModule.cs
  77. 24
      framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobContainerConfigurationExtensions.cs
  78. 110
      framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs
  79. 39
      framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfiguration.cs
  80. 9
      framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfigurationNames.cs
  81. 22
      framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/DefaultAzureBlobNameCalculator.cs
  82. 7
      framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/IAzureBlobNameCalculator.cs
  83. 2
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderArgs.cs
  84. 7
      framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyCrudAppService.cs
  85. 5
      framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs
  86. 4
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Services/DomainService.cs
  87. 14
      framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/MySQL/AbpEntityFrameworkCoreMySQLModule.cs
  88. 14
      framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/Oracle/Devart/AbpEntityFrameworkCoreOracleDevartModule.cs
  89. 14
      framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/PostgreSql/AbpEntityFrameworkCorePostgreSqlModule.cs
  90. 14
      framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo/Abp/EntityFrameworkCore/SqlServer/AbpEntityFrameworkCoreSqlServerModule.cs
  91. 34
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EfCoreAsyncQueryableProvider.cs
  92. 4
      framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs
  93. 18
      framework/src/Volo.Abp.Guids/Volo/Abp/Guids/AbpSequentialGuidGeneratorOptions.cs
  94. 2
      framework/src/Volo.Abp.Guids/Volo/Abp/Guids/SequentialGuidGenerator.cs
  95. 2
      framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs
  96. 35
      framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoDbAsyncQueryableProvider.cs
  97. 5
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs
  98. 1
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoExtensions.cs
  99. 2
      framework/src/Volo.Abp.RabbitMQ/Volo.Abp.RabbitMQ.csproj
  100. 53
      framework/src/Volo.Abp.Threading/Volo/Abp/Linq/AsyncQueryableExecuter.cs

7
apiSpec/Microsoft_AspNetCore_Routing_AbpEndpointRouterOptions.md

@ -0,0 +1,7 @@
---
uid: Microsoft.AspNetCore.Routing.AbpEndpointRouterOptions
summary: '*Summary test'
---
*Information Test*

23
docs/en/API/Application-Configuration.md

@ -0,0 +1,23 @@
# Application Configuration Endpoint
ABP Framework provides a pre-built and standard endpoint that contains some useful information about the application/service. Here, the list of some fundamental information at this endpoint:
* [Localization](Localization.md) values, supported and the current language of the application.
* Available and granted [policies](Authorization.md) (permissions) for the current user.
* [Setting](Settings.md) values for the current user.
* Info about the [current user](CurrentUser.md) (like id and user name).
* Info about the current [tenant](Multi-Tenancy.md) (like id and name).
* [Time zone](Timing.md) information for the current user and the [clock](Timing.md) type of the application.
## HTTP API
If you navigate to the `/api/abp/application-configuration` URL of an ABP Framework based web application or HTTP Service, you can access the configuration as a JSON object. This endpoint is useful to create the client of your application.
## Script
For ASP.NET Core MVC (Razor Pages) applications, the same configuration values are also available on the JavaScript side. `/Abp/ApplicationConfigurationScript` is the URL of the script that is auto-generated based on the HTTP API above.
See the [JavaScript API document](../UI/AspNetCore/JavaScript-API/Index.md) for the ASP.NET Core UI.
Other UI types provide services native to the related platform. For example, see the [Angular UI localization documentation](../UI/Angular/Localization.md) to learn how to use the localization values exposes by this endpoint.

3
docs/en/API/JavaScript-API/Auth.md

@ -1,3 +0,0 @@
# abp.auth JavaScript API
TODO

4
docs/en/Application-Services.md

@ -2,7 +2,7 @@
Application services are used to implement the **use cases** of an application. They are used to **expose domain logic to the presentation layer**.
An Application Service is called from the presentation layer (optionally) with a **DTO (Data Transfer Object)** as the parameter. It uses domain objects to **perform some specific business logic** and (optionally) returns a DTO back to the presentation layer. Thus, the presentation layer is completely **isolated** from domain layer.
An Application Service is called from the presentation layer (optionally) with a **DTO ([Data Transfer Object](Data-Transfer-Objects.md))** as the parameter. It uses domain objects to **perform some specific business logic** and (optionally) returns a DTO back to the presentation layer. Thus, the presentation layer is completely **isolated** from domain layer.
## Example
@ -205,7 +205,7 @@ See the [object to object mapping document](Object-To-Object-Mapping.md) for mor
## Validation
Inputs of application service methods are automatically validated (like ASP.NET Core controller actions). You can use the standard data annotation attributes or custom validation method to perform the validation. ABP also ensures that the input is not null.
Inputs of application service methods are automatically validated (like ASP.NET Core controller actions). You can use the standard data annotation attributes or a custom validation method to perform the validation. ABP also ensures that the input is not null.
See the [validation document](Validation.md) for more.

2
docs/en/AspNet-Boilerplate-Migration-Guide.md

@ -595,7 +595,7 @@ ABP Framework separates it and provides the setting management module (pre-added
ASP.NET Boilerplate has a static `Clock` service ([see](https://aspnetboilerplate.com/Pages/Documents/Timing)) which is used to abstract the `DateTime` kind, so you can easily switch between Local and UTC times. You don't inject it, but just use the `Clock.Now` static method to obtain the current time.
ABP Framework has the `IClock` service ([see](Clock.md)) which has a similar goal, but now you need to inject it whenever you need it.
ABP Framework has the `IClock` service ([see](Timing.md)) which has a similar goal, but now you need to inject it whenever you need it.
### Event Bus

3
docs/en/AspNetCore/JavaScript-API/Auth.md

@ -1,3 +0,0 @@
This document has moved.
[Click to navigate to JavaScript Auth document](../../API/JavaScript-API/Auth.md)

3
docs/en/AspNetCore/JavaScript-API/Index.md

@ -1,3 +0,0 @@
This document has moved.
[Click to navigate to JavaScript API document](../../API/JavaScript-API/Index.md)

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

@ -1,3 +1,59 @@
# BLOB Storing Azure Provider
This feature will be available with v3.0!
BLOB Storing Azure Provider can store BLOBs in [Azure Blob storage](https://azure.microsoft.com/en-us/services/storage/blobs/).
> 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 Azure BLOB as the storage provider.
## Installation
Use the ABP CLI to add [Volo.Abp.BlobStoring.Azure](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Azure) 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.Azure` package.
* Run `abp add-package Volo.Abp.BlobStoring.Azure` command.
If you want to do it manually, install the [Volo.Abp.BlobStoring.Azure](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Azure) NuGet package to your project and add `[DependsOn(typeof(AbpBlobStoringAzureModule))]` 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 azure storage provider by default**
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containerscontainer.UseAzure(azure =>
{
azure.ConnectionString = "your azure connection string";
azure.ContainerName = "your azure container name";
azure.CreateContainerIfNotExists = false;
});
});
````
> See the [BLOB Storing document](Blob-Storing.md) to learn how to configure this provider for a specific container.
### Options
* **ConnectionString** (string): A connection string includes the authorization information required for your application to access data in an Azure Storage account at runtime using Shared Key authorization. Please refer to Azure documentation: https://docs.microsoft.com/en-us/azure/storage/common/storage-configure-connection-string
* **ContainerName** (string): You can specify the container name in azure. If this is not specified, it uses the name of the BLOB container defined with the `BlogContainerName` attribute (see the [BLOB storing document](Blob-Storing.md)). Please note that Azure has some **rules for naming containers**. A container name must be a valid DNS name, conforming to the [following naming rules](https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names):
* Container names must start or end with a letter or number, and can contain only letters, numbers, and the dash (-) character.
* Every dash (-) character must be immediately preceded and followed by a letter or number; consecutive dashes are not permitted in container names.
* All letters in a container name must be **lowercase**.
* Container names must be from **3** through **63** characters long.
* **CreateContainerIfNotExists** (bool): Default value is `false`, If a container does not exist in azure, `AzureBlobProvider` will try to create it.
## Azure Blob Name Calculator
Azure Blob Provider organizes BLOB name and implements some conventions. The full name of a BLOB is determined by the following rules by default:
* Appends `host` string 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>` string if current tenant is not `null`.
* Appends the BLOB name.
## Other Services
* `AzureBlobProvider` is the main service that implements the Azure BLOB storage provider, if you want to override/replace it via [dependency injection](Dependency-Injection.md) (don't replace `IBlobProvider` interface, but replace `AzureBlobProvider` class).
* `IAzureBlobNameCalculator` is used to calculate the full BLOB name (that is explained above). It is implemented by the `DefaultAzureBlobNameCalculator` by default.

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

@ -64,7 +64,7 @@ Configure<AbpBlobStoringOptions>(options =>
If you want to provide a simpler configuration, create an extension method for the `BlobContainerConfiguration` class:
````
````csharp
public static class MyBlobContainerConfigurationExtensions
{
public static BlobContainerConfiguration UseMyCustomBlobProvider(
@ -174,4 +174,4 @@ public class MyCustomBlobProvider : BlobProviderBase, ITransientDependency
## 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.
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.

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

@ -55,6 +55,8 @@ If you want to use a separate database for BLOB storage, use the `AbpBlobStoring
### Configuring the Containers
If you are using only the database storage provider, you don't need to manually configure it, since it is automatically done. If you are using multiple storage providers, you may want to configure it.
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**

3
docs/en/Clock.md

@ -1,3 +0,0 @@
# Clock
TODO

2
docs/en/CurrentUser.md

@ -59,7 +59,7 @@ Here are the fundamental properties of the `ICurrentUser` interface:
* **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.
* **EmailVerified** (bool): Returns `true`, if the email address 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.

159
docs/en/Data-Seeding.md

@ -1,3 +1,160 @@
# Data Seeding
TODO
## Introduction
Some applications (or modules) using a database may need to have some **initial data** to be able to properly start and run. For example, an **admin user** & roles must be available at the beginning. Otherwise you can not **login** to the application to create new users and roles.
Data seeding is also useful for [testing](Testing.md) purpose, so your automatic tests can assume some initial data available in the database.
### Why a Data Seed System?
While EF Core Data Seeding system provides a way, it is very limited and doesn't cover production scenarios. Also, it is only for EF Core.
ABP Framework provides a data seed system that is;
* **Modular**: Any [module](Module-Development-Basics.md) can silently contribute to the data seeding process without knowing and effecting each other. In this way, a module seeds its own initial data.
* **Database Independent**: It is not only for EF Core, it also works for other database providers (like [MongoDB](MongoDB.md)).
* **Production Ready**: It solves the problems on production environments. See the "*On Production*" section below.
* **Dependency Injection**: It takes the full advantage of dependency injection, so you can use any internal or external service while seeding the initial data. Actually, you can do much more than data seeding.
## IDataSeedContributor
`IDataSeedContributor` is the interface that should be implemented in order to seed data to the database.
**Example: Seed one initial book to the database if there is no book**
````csharp
using System;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Guids;
namespace Acme.BookStore
{
public class BookStoreDataSeedContributor
: IDataSeedContributor, ITransientDependency
{
private readonly IRepository<Book, Guid> _bookRepository;
private readonly IGuidGenerator _guidGenerator;
public BookStoreDataSeedContributor(
IRepository<Book, Guid> bookRepository,
IGuidGenerator guidGenerator)
{
_bookRepository = bookRepository;
_guidGenerator = guidGenerator;
}
public async Task SeedAsync(DataSeedContext context)
{
if (await _bookRepository.GetCountAsync() > 0)
{
return;
}
var book = new Book(
id: _guidGenerator.Create(),
name: "The Hitchhiker's Guide to the Galaxy",
type: BookType.ScienceFiction,
publishDate: new DateTime(1979, 10, 12),
price: 42
);
await _bookRepository.InsertAsync(book);
}
}
}
````
* `IDataSeedContributor` defines the `SeedAsync` method to execute the **data seed logic**.
* It is typical to **check database** if the seeding data is already present.
* You can **inject** service and perform any logic needed to seed the data.
> Data seed contributors are automatically discovered by the ABP Framework and executed as a part of the data seed process.
### DataSeedContext
`DataSeedContext` contains `TenantId` if your application is [multi-tenant](Multi-Tenancy.md), so you can use this value while inserting data or performing custom logic based on the tenant.
`DataSeedContext` also contains name-value style configuration parameters for passing to the seeder contributors from the `IDataSeeder`.
## Modularity
An application can have multiple data seed contributor (`IDataSeedContributor`) class. So, any reusable module can also implement this interface to seed its own initial data.
For example, the [Identity Module](Modules/Identity.md) has a data seed contributor that creates an admin role and admin user and assign all the permissions.
## IDataSeeder
> You typically never need to directly use the `IDataSeeder` service since it is already done if you've started with the [application startup template](Startup-Templates/Application.md). But its suggested to read it to understand the design behind the data seed system.
`IDataSeeder` is the main service that is used to seed initial data. It is pretty easy to use;
````csharp
public class MyService : ITransientDependency
{
private readonly IDataSeeder _dataSeeder;
public MyService(IDataSeeder dataSeeder)
{
_dataSeeder = dataSeeder;
}
public async Task FooAsync()
{
await _dataSeeder.SeedAsync();
}
}
````
You can [inject](Dependency-Injection.md) the `IDataSeeder` and use it to seed the initial data when you need. It internally calls all the `IDataSeedContributor` implementations to complete the data seeding.
It is possible to send named configuration parameters to the `SeedAsync` method as shown below:
````csharp
await _dataSeeder.SeedAsync(
new DataSeedContext()
.WithProperty("MyProperty1", "MyValue1")
.WithProperty("MyProperty2", 42)
);
````
Then the data seed contributors can access to these properties via the `DataSeedContext` explained before.
If a module needs to a parameter, it should be declared on the [module documentation](Modules/Index.md). For example, the [Identity Module](Modules/Identity.md) can use `AdminEmail` and `AdminPassword` parameters if you provide (otherwise uses the default values).
### Where & How to Seed Data?
It is important to understand where & how to execute the `IDataSeeder.SeedAsync()`?
#### On Production
The [application startup template](Startup-Templates/Application.md) comes with a *YourProjectName***.DbMigrator** project (Acme.BookStore.DbMigrator on the picture below), which is a **console application** that is responsible to **migrate** the database schema (for relational databases) and **seed** the initial data:
![bookstore-visual-studio-solution-v3](images/bookstore-visual-studio-solution-v3.png)
This console application is properly configured for you. It even supports **multi-tenant** scenarios where each tenant has its own database (migrates & seeds all necessary databases).
It is expected to run this DbMigrator application whenever you **deploy a new version** of your solution to the server. It will migrate your **database schema** (create new tables/fields... etc.) and **seed new initial data** needed to properly run the new version of your solution. Then you can deploy/start your actual application.
Even if you are using MongoDB or another NoSQL database (that doesn't need to schema migrations), it is recommended to use the DbMigrator application to seed your data or perform your data migration.
Having such a separate console application has several advantages;
* You can **run it before** updating your application, so your application will run on the ready database.
* Your application **starts faster** compared to if it seeds the initial data itself.
* Your application can properly run on a **clustered environment** (where multiple instances of your application run concurrently). If you seed data on application startup you would have conflicts in this case.
#### On Development
We suggest the same way on development. Run the DbMigrator console application whenever you [create a database migration](https://docs.microsoft.com/en-us/ef/ef6/modeling/code-first/migrations/) (using EF Core `Add-Migration` command, for example) or change the data seed code (will be explained later).
> You can continue to use the standard `Update-Database` command for EF Core, but it will not seed if you've created a new seed data.
#### On Testing
You probably want to seed the data also for automated [testing](Testing.md), so want to use the `IDataSeeder.SeedAsync()`. In the [application startup template](Startup-Templates/Application.md), it is done in the [OnApplicationInitialization](Module-Development-Basics.md) method of the *YourProjectName*TestBaseModule class of the TestBase project.
In addition to the standard seed data (that is also used on production), you may want to seed additional data unique to the automated tests. If so, you can create a new data seed contributor in the test project to have more data to work on.

281
docs/en/Data-Transfer-Objects.md

@ -1,3 +1,280 @@
## Data Transfer Objects
# Data Transfer Objects
TODO
## Introduction
**Data Transfer Objects** (DTO) are used to transfer data between the **Application Layer** and the **Presentation Layer** or other type of clients.
Typically, an [application service](Application-Services.md) is called from the presentation layer (optionally) with a **DTO** as the parameter. It uses domain objects to **perform some specific business logic** and (optionally) returns a DTO back to the presentation layer. Thus, the presentation layer is completely **isolated** from domain layer.
### The Need for DTOs
> **You can skip this section** if you feel that you know and confirm the benefits of using DTOs.
At first, creating a DTO class for each application service method can be seen as tedious and time-consuming work. However, they can save your application if you correctly use them. Why & how?
#### Abstraction of the Domain Layer
DTOs provide an efficient way of **abstracting domain objects** from the presentation layer. In effect, your **layers** are correctly separated. If you want to change the presentation layer completely, you can continue with the existing application and domain layers. Alternatively, you can re-write your domain layer, completely change the database schema, entities and O/RM framework, all without changing the presentation layer. This, of course, is as long as the contracts (method signatures and DTOs) of your application services remain unchanged.
#### Data Hiding
Say you have a `User` entity with the properties Id, Name, EmailAddress and Password. If a `GetAllUsers()` method of a `UserAppService` returns a `List<User>`, anyone can access the passwords of all your users, even if you do not show it on the screen. It's not just about security, it's about data hiding. Application services should return only what it needs by the presentation layer (or client). Not more, not less.
#### Serialization & Lazy Load Problems
When you return data (an object) to the presentation layer, it's most likely serialized. For example, in a REST API that returns JSON, your object will be serialized to JSON and sent to the client. Returning an Entity to the presentation layer can be problematic in that regard, especially if you are using a relational database and an ORM provider like Entity Framework Core. How?
In a real-world application, your entities may have references to each other. The `User` entity can have a reference to it's `Role`s. If you want to serialize `User`, its `Role`s are also serialized. The `Role` class may have a `List<Permission>` and the `Permission` class can has a reference to a `PermissionGroup` class and so on... Imagine all of these objects being serialized at once. You could easily and accidentally serialize your whole database! Also, if your objects have circular references, they may **not** be serialized at all.
What's the solution? Marking properties as `NonSerialized`? No, you can not know when it should be serialized and when it shouldn't be. It may be needed in one application service method, and not needed in another. Returning safe, serializable, and specially designed DTOs is a good choice in this situation.
Almost all O/RM frameworks support lazy-loading. It's a feature that loads entities from the database when they're needed. Say a `User` class has a reference to a `Role` class. When you get a `User` from the database, the `Role` property (or collection) is not filled. When you first read the `Role` property, it's loaded from the database. So, if you return such an Entity to the presentation layer, it will cause it to retrieve additional entities from the database by executing additional queries. If a serialization tool reads the entity, it reads all properties recursively and again your whole database can be retrieved (if there are relations between entities).
More problems can arise if you use Entities in the presentation layer. **It's best not to reference the domain/business layer assembly in the presentation layer.**
If you are convinced about using DTOs, we can continue to what ABP Framework provides and suggests about DTOs.
> ABP doesn't force you to use DTOs, however using DTOs is **strongly suggested as a best practice**.
## Standard Interfaces & Base Classes
A DTO is a simple class that has no dependency and you can design it in any way. However, ABP introduces some **interfaces** to determine the **conventions** for naming **standard properties** and **base classes** to **don't repeat yourself** while declaring **common properties**.
**None of them are required**, but using them **simplifies and standardizes** your application code.
### Entity Related DTOs
You typically create DTOs corresponding to your entities, which results similar classes to your entities. ABP Framework provides some base classes to simplify while creating such DTOs.
#### EntityDto
`IEntityDto<TKey>` is a simple interface that only defines an `Id` property. You can implement it or inherit from the `EntityDto<TKey>` for your DTOs that matches to an [entity](Entities.md).
**Example:**
````csharp
using System;
using Volo.Abp.Application.Dtos;
namespace AbpDemo
{
public class ProductDto : EntityDto<Guid>
{
public string Name { get; set; }
//...
}
}
````
#### Audited DTOs
If your entity inherits from audited entity classes (or implements auditing interfaces), you can use the following base classes to create your DTOs:
* `CreationAuditedEntityDto`
* `CreationAuditedEntityWithUserDto`
* `AuditedEntityDto`
* `AuditedEntityWithUserDto`
* `FullAuditedEntityDto`
* `FullAuditedEntityWithUserDto`
#### Extensible DTOs
If you want to use the [object extension system](Object-Extensions.md) for your DTOs, you can use or inherit from the following DTO classes:
* `ExtensibleObject` implements the `IHasExtraProperties` (other classes inherits this class).
* `ExtensibleEntityDto`
* `ExtensibleCreationAuditedEntityDto`
* `ExtensibleCreationAuditedEntityWithUserDto`
* `ExtensibleAuditedEntityDto`
* `ExtensibleAuditedEntityWithUserDto`
* `ExtensibleFullAuditedEntityDto`
* `ExtensibleFullAuditedEntityWithUserDto`
### List Results
It is common to return a list of DTOs to the client. `IListResult<T>` interface and `ListResultDto<T>` class is used to make it standard.
The definition of the `IListResult<T>` interface:
````csharp
public interface IListResult<T>
{
IReadOnlyList<T> Items { get; set; }
}
````
**Example: Return a list of products**
````csharp
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
namespace AbpDemo
{
public class ProductAppService : ApplicationService, IProductAppService
{
private readonly IRepository<Product, Guid> _productRepository;
public ProductAppService(IRepository<Product, Guid> productRepository)
{
_productRepository = productRepository;
}
public async Task<ListResultDto<ProductDto>> GetListAsync()
{
//Get entities from the repository
List<Product> products = await _productRepository.GetListAsync();
//Map entities to DTOs
List<ProductDto> productDtos =
ObjectMapper.Map<List<Product>, List<ProductDto>>(products);
//Return the result
return new ListResultDto<ProductDto>(productDtos);
}
}
}
````
You could simply return the `productDtos` object (and change the method return type) and it has nothing wrong. Returning a `ListResultDto` makes your `List<ProductDto>` wrapped into another object as an `Items` property. This has one advantage: You can later add more properties to your return value without breaking your remote clients (when they get the value as a JSON result). So, it is especially suggested when you are developing reusable application modules.
### Paged & Sorted List Results
It is more common to request a paged list from server and return a paged list to the client. ABP defines a few interface and classes to standardize it:
#### Input (Request) Types
The following interfaces and classes is to standardize the input sent by the clients.
* `ILimitedResultRequest`: Defines a `MaxResultCount` (`int`) property to request a limited result from the server.
* `IPagedResultRequest`: Inherits from the `ILimitedResultRequest` (so it inherently has the `MaxResultCount` property) and defines a `SkipCount` (`int`) to declare the skip count while requesting a paged result from the server.
* `ISortedResultRequest`: Defines a `Sorting` (`string`) property to request a sorted result from the server. Sorting value can be "*Name*", "*Name DESC*", "*Name ASC, Age DESC*"... etc.
* `IPagedAndSortedResultRequest` inherits from both of the `IPagedResultRequest` and `ISortedResultRequest`, so has `MaxResultCount`, `SkipCount` and `Sorting` properties.
Instead of implementing the interfaces manually, it is suggested to inherit one of the following base DTO classes:
* `LimitedResultRequestDto` implements `ILimitedResultRequest`.
* `PagedResultRequestDto` implements `IPagedResultRequest` (and inherits from the `LimitedResultRequestDto`).
* `PagedAndSortedResultRequestDto` implements `IPagedAndSortedResultRequest` (and inherit from the `PagedResultRequestDto`).
##### Max Result Count
`LimitedResultRequestDto` (and inherently the others) limits and validates the `MaxResultCount` by the following rules;
* If the client doesn't set `MaxResultCount`, it is assumed as **10** (the default page size). This value can be changed by setting the `LimitedResultRequestDto.DefaultMaxResultCount` static property.
* If the client sends `MaxResultCount` greater than **1,000**, it produces a **validation error**. It is important to protect the server from abuse of the service. If you want, you can change this value by setting the `LimitedResultRequestDto.MaxMaxResultCount` static property.
Static properties suggested to be set on application startup since they are static (global).
#### Output (Response) Types
The following interfaces and classes is to standardize the output sent to the clients.
* `IHasTotalCount` defines a `TotalCount` (`long`) property to return the total count of the records in case of paging.
* `IPagedResult<T>` inherits from the `IListResult<T>` and `IHasTotalCount`, so it has the `Items` and `TotalCount` properties.
Instead of implementing the interfaces manually, it is suggested to inherit one of the following base DTO classes:
* `PagedResultDto<T>` inherits from the `ListResultDto<T>` and also implements the `IPagedResult<T>`.
**Example: Request a paged & sorted result from server and return a paged list**
````csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
namespace AbpDemo
{
public class ProductAppService : ApplicationService, IProductAppService
{
private readonly IRepository<Product, Guid> _productRepository;
public ProductAppService(IRepository<Product, Guid> productRepository)
{
_productRepository = productRepository;
}
public async Task<PagedResultDto<ProductDto>> GetListAsync(
PagedAndSortedResultRequestDto input)
{
//Create the query
var query = _productRepository
.OrderBy(input.Sorting)
.Skip(input.SkipCount)
.Take(input.MaxResultCount);
//Get total count from the repository
var totalCount = await query.CountAsync();
//Get entities from the repository
List<Product> products = await query.ToListAsync();
//Map entities to DTOs
List<ProductDto> productDtos =
ObjectMapper.Map<List<Product>, List<ProductDto>>(products);
//Return the result
return new PagedResultDto<ProductDto>(totalCount, productDtos);
}
}
}
````
ABP Framework also defines a `PageBy` extension method (that is compatible with the `IPagedResultRequest`) that can be used instead of `Skip` + `Take` calls:
````csharp
var query = _productRepository
.OrderBy(input.Sorting)
.PageBy(input);
````
> Notice that we added `Volo.Abp.EntityFrameworkCore` package to the project to be able to use the `ToListAsync` and `CountAsync` methods since they are not included in the standard LINQ, but defined by the Entity Framework Core.
See also the [repository documentation](Repositories.md) to if you haven't understood the example code.
## Related Topics
### Validation
Inputs of [application service](Application-Services.md) methods, controller actions, page model inputs... are automatically validated. You can use the standard data annotation attributes or a custom validation method to perform the validation.
See the [validation document](Validation.md) for more.
### Object to Object Mapping
When you create a DTO that is related to an entity, you generally need to map these objects. ABP provides an object to object mapping system to simplify the mapping process. See the following documents:
* [Object to Object Mapping document](Object-To-Object-Mapping.md) covers all the features.
* [Application Services document](Application-Services.md) provides a full example.
## Best Practices
You are free to design your DTO classes. However, there are some best practices & suggestions that you may want to follow.
### Common Principles
* DTOs should be **well serializable** since they are generally serialized and deserialized (to JSON or other format). It is suggested to have an empty (parameterless) public constructor if you have another constructor with parameter(s).
* DTOs **should not contain any business logic**, except some formal [validation](Validation.md) code.
* Do not inherit DTOs from entities and **do not reference to entities**. The [application startup template](Startup-Templates/Application.md) already prevents it by separating the projects.
* If you use an auto [object to object mapping](Object-To-Object-Mapping.md) library, like AutoMapper, enable the **mapping configuration validation** to prevent potential bugs.
### Input DTO Principles
* Define only the **properties needed** for the use case. Do not include properties not used for the use case, which confuses developers if you do so.
* **Don't reuse** input DTOs among different application service methods. Because, different use cases will need to and use different properties of the DTO which results some properties are not used in some cases and that makes harder to understand and use the services and causes potential bugs in the future.
### Output DTO Principles
* You can **reuse output DTOs** if you **fill all the properties** on all the cases.

4
docs/en/Domain-Driven-Design.md

@ -2,7 +2,7 @@
## What is DDD?
ABP framework provides an **infrastructure** to make **DDD** based development easier to implement. DDD is [defined in the Wikipedia](https://en.wikipedia.org/wiki/Domain-driven_design) as below:
ABP framework provides an **infrastructure** to make **Domain Driven Design** based development easier to implement. DDD is [defined in the Wikipedia](https://en.wikipedia.org/wiki/Domain-driven_design) as below:
> **Domain-driven design** (**DDD**) is an approach to software development for complex needs by connecting the implementation to an evolving model. The premise of domain-driven design is the following:
>
@ -16,7 +16,7 @@ ABP follows DDD principles and patterns to achieve a layered application model w
- **Presentation Layer**: Provides an interface to the user. Uses the *Application Layer* to achieve user interactions.
- **Application Layer**: Mediates between the Presentation and Domain Layers. Orchestrates business objects to perform specific application tasks. Implements use cases as the application logic.
- **Domain Layer**: Includes business objects and their business rules. This is the heart of the application.
- **Domain Layer**: Includes business objects and the core (domain) business rules. This is the heart of the application.
- **Infrastructure Layer**: Provides generic technical capabilities that support higher layers mostly using 3rd-party libraries.
## Contents

8
docs/en/Entities.md

@ -26,9 +26,9 @@ public class Book : Entity<Guid>
If your entity's Id type is `Guid`, there are some good practices to implement:
* Create a constructor that gets the Id as a parameter and passes to the base class.
* If you don't set a GUID Id, ABP Framework sets it on save, but it is good to have a valid Id on the entity even before saving it to the database.
* If you create an entity with a constructor that takes parameters, also create a `protected` empty constructor. This is used while your database provider reads your entity from the database (on deserialization).
* Don't use the `Guid.NewGuid()` to set the Id! Use [the `IGuidGenerator` service](Guid-Generation.md) while passing the Id from the code that creates the entity. `IGuidGenerator` optimized to generate sequential GUIDs, which is critical for clustered indexes in the relational databases.
* If you don't set a GUID Id, **ABP Framework sets it on save**, but it is good to have a valid Id on the entity even before saving it to the database.
* If you create an entity with a constructor that takes parameters, also create a `private` or `protected` empty constructor. This is used while your database provider reads your entity from the database (on deserialization).
* Don't use the `Guid.NewGuid()` to set the Id! **Use [the `IGuidGenerator` service](Guid-Generation.md)** while passing the Id from the code that creates the entity. `IGuidGenerator` optimized to generate sequential GUIDs, which is critical for clustered indexes in the relational databases.
An example entity:
@ -382,7 +382,7 @@ The way to store this dictionary in the database depends on the database provide
Extra Properties system is especially useful if you are using a **re-usable module** that defines an entity inside and you want to get/set some data related to this entity in an easy way.
You normally **don't need** to this system for your own entities, because it has the following drawbacks:
You typically **don't need** to use this system for your own entities, because it has the following drawbacks:
* It is **not fully type safe** since it works with strings as property names.
* It is **not easy to [auto map](Object-To-Object-Mapping.md)** these properties from/to other objects.

4
docs/en/Guid-Generation.md

@ -106,4 +106,6 @@ Configure<AbpSequentialGuidGeneratorOptions>(options =>
{
options.DefaultSequentialGuidType = SequentialGuidType.SequentialAsBinary;
});
````
````
> EF Core [integration packages](https://docs.abp.io/en/abp/latest/Entity-Framework-Core-Other-DBMS) sets this option to a proper value for the related DBMS. So, most of the times, you don't need to set this option if you are using these integration packages.

3
docs/en/Json.md

@ -0,0 +1,3 @@
# JSON
TODO

29
docs/en/Modules/Docs.md

@ -58,14 +58,18 @@ Now an empty ABP project has been created! You can now run your project and see
To login your website enter `admin` as the username and `1q2w3E*` as the password.
### 2- Referencing Docs Module Packages
### 3- Installation Module
Docs module packages are hosted on NuGet. There are 4 packages that needs be to installed to your application. Each package has to be installed to the relevant project.
#### 3.1- Use ABP CLI
It is recommended to use the ABP CLI to install the module, open the CMD window in the solution file (`.sln`) directory, and run the following command:
`abp add-module Volo.Docs`
#### 3.2- Manually install
Or you can also manually install nuget package to each project:
* Install [Volo.Docs.Domain](https://www.nuget.org/packages/Volo.Docs.Domain/) nuget package to `Acme.MyProject.Domain` project.
@ -84,7 +88,7 @@ Or you can also manually install nuget package to each project:
`Install-Package Volo.Docs.Web`
### 3- Adding Module Dependencies
##### 3.2.1- Adding Module Dependencies
An ABP module must declare `[DependsOn]` attribute if it has a dependency upon another module. Each module has to be added in`[DependsOn]` attribute to the relevant project.
@ -165,6 +169,27 @@ An ABP module must declare `[DependsOn]` attribute if it has a dependency upon a
}
```
##### 3.2.2- Adding NPM Package
Open `package.json` and add `@abp/docs": "^2.9.0` as shown below:
```json
{
"version": "1.0.0",
"name": "my-app",
"private": true,
"dependencies": {
"@abp/aspnetcore.mvc.ui.theme.basic": "^2.9.0",
"@abp/docs": "^2.9.0"
}
}
```
Then open the command line terminal in the `Acme.MyProject.Web` project folder and run the following command:
1. `yarn`
2. `gulp`
### 4- Database Integration
#### 4.1- Entity Framework Integration

8
docs/en/Modules/Virtual-File-Explorer.md

@ -8,19 +8,21 @@ Virtual File Explorer Module provided a simple UI to view all files in [virtual
### Installation
#### 1- Referencing Virtual File Explorer Module Packages
#### 1- Use ABP CLI
It is recommended to use the ABP CLI to install the module, open the CMD window in the solution file (`.sln`) directory, and run the following command:
`abp add-module Volo.VirtualFileExplorer`
#### 2- Manually install
Or you can also manually install nuget package to `Acme.MyProject.Web` project:
* Install [Volo.Abp.VirtualFileExplorer.Web](https://www.nuget.org/packages/Volo.Abp.VirtualFileExplorer.Web/) nuget package to `Acme.MyProject.Web` project.
`Install-Package Volo.Abp.VirtualFileExplorer.Web`
#### 2- Adding Module Dependencies
##### 2.1- Adding Module Dependencies
* Open `MyProjectWebModule.cs`and add `typeof(AbpVirtualFileExplorerWebModule)` as shown below;
@ -40,7 +42,7 @@ Or you can also manually install nuget package to `Acme.MyProject.Web` project:
}
```
#### 3- Adding NPM Package
##### 2.2- Adding NPM Package
* Open `package.json` and add `@abp/virtual-file-explorer": "^2.9.0` as shown below:

113
docs/en/Timing.md

@ -0,0 +1,113 @@
# Timing
Working with times & [time zones](https://en.wikipedia.org/wiki/Time_zone) is always tricky, especially if you need to build a **global system** that is used by users in **different time zones**.
ABP provides a basic infrastructure to make it easy and handle automatically wherever possible. This document covers the ABP Framework services and systems related to time and time zones.
> If you are creating a local application that runs in a single time zone region, you may not need all these systems. But even in this case, it is suggested to use the `IClock` service introduced in this document.
## IClock
`DateTime.Now` returns a `DateTime` object with the **local date & time of the server**. A `DateTime` object **doesn't store the time zone information**. So, you can not know the **absolute date & time** stored in this object. You can only make **assumptions**, like assuming that it was created in UTC+05 time zone. The things especially gets complicated when you save this value to a database and read later, or send it to a client in a **different time zone**.
One solution to this problem is always use `DateTime.UtcNow` and assume all `DateTime` objects as UTC time. In this was, you can convert it to the time zone of the target client when needed.
`IClock` provides an abstraction while getting the current time, so you can control the kind of the date time (UTC or local) in a single point in your application.
**Example: Getting the current time**
````csharp
using Volo.Abp.DependencyInjection;
using Volo.Abp.Timing;
namespace AbpDemo
{
public class MyService : ITransientDependency
{
private readonly IClock _clock;
public MyService(IClock clock)
{
_clock = clock;
}
public void Foo()
{
//Get the current time!
var now = _clock.Now;
}
}
}
````
* Inject the `IClock` service when you need to get the current time. Common base classes (like ApplicationService) already injects it and provides as a base property - so, you can directly use as `Clock`.
* Use the `Now` property to get the current time.
> Most of the times, `IClock` is the only service you need to know and use in your application.
### Clock Options
`AbpClockOptions` is the [options](Options.md) class that used to set the clock kind.
**Example: Use UTC Clock**
````csharp
Configure<AbpClockOptions>(options =>
{
options.Kind = DateTimeKind.Utc;
});
````
Write this inside the `ConfigureServices` method of your [module](Module-Development-Basics.md).
> Default `Kind` is `Unspecified`, that actually make the Clock as it doesn't exists at all. Either make it `Utc` or `Local` if you want to get benefit of the Clock system.
### DateTime Normalization
Other important function of the `IClock` is to normalize `DateTime` objects.
**Example usage:**
````csharp
DateTime dateTime = ...; //Get from somewhere
var normalizedDateTime = Clock.Normalize(dateTime)
````
`Normalize` method works as described below:
* Converts the given `DateTime` to the UTC (by using the `DateTime.ToUniversalTime()` method) if current Clock is UTC and given `DateTime` is local.
* Converts the given `DateTime` to the local (by using the `DateTime.ToLocalTime()` method) if current Clock is local and given `DateTime` is UTC.
* Sets `Kind` of the given `DateTime` (using the `DateTime.SpecifyKind(...)` method) to the `Kind` of the current Clock if given `DateTime`'s `Kind` is `Unspecified`.
`Normalize` method is used by the ABP Framework when the it gets a `DateTime` that is not created by `IClock.Now` and may not be compatible with the current Clock type. Examples;
* `DateTime` type binding in the ASP.NET Core MVC model binding.
* Saving data to and reading data from database via [Entity Framework Core](Entity-Framework-Core.md).
* Working with `DateTime` objects on [JSON deserialization](Json.md).
#### DisableDateTimeNormalization Attribute
`DisableDateTimeNormalization` attribute can be used to disable the normalization operation for desired classes or properties.
### Other IClock Properties
In addition to the `Now`, `IClock` service has the following properties:
* `Kind`: Returns a `DateTimeKind` for the currently used clock type (`DateTimeKind.Utc`, `DateTimeKind.Local` or `DateTimeKind.Unspecified`).
* `SupportsMultipleTimezone`: Returns `true` if currently used clock is UTC.
## Time Zones
This section covers the ABP Framework infrastructure related to managing time zones.
### TimeZone Setting
ABP Framework defines **a setting**, named `Abp.Timing.Timezone`, that can be used to set and get the time zone for a user, [tenant](Multi-Tenancy.md) or globally for the application. The default value is `UTC`.
See the [setting documentation](Settings.md) to learn more about the setting system.
### ITimezoneProvider
`ITimezoneProvider` is a service to simple convert [Windows Time Zone Id](https://support.microsoft.com/en-us/help/973627/microsoft-time-zone-index-values) values to [Iana Time Zone Name](https://www.iana.org/time-zones) values and vice verse. It also provides methods to get list of these time zones and get a `TimeZoneInfo` with a given name.
It has been implemented using the [TimeZoneConverter](https://github.com/mj1856/TimeZoneConverter) library.

5
docs/en/API/JavaScript-API/Index.md → docs/en/UI/AspNetCore/JavaScript-API/Index.md

@ -5,7 +5,7 @@ ABP provides some JavaScript APIs for ASP.NET Core MVC / Razor Pages application
## APIs
* abp.ajax
* [abp.auth](Auth.md)
* abp.auth
* abp.currentUser
* abp.dom
* abp.event
@ -20,5 +20,4 @@ ABP provides some JavaScript APIs for ASP.NET Core MVC / Razor Pages application
* abp.utils
* abp.ResourceLoader
* abp.WidgetManager
* Other APIs
* Other APIs

37
docs/en/UI/AspNetCore/Tag-Helpers/Button-groups.md

@ -0,0 +1,37 @@
# Button groups
## Introduction
`abp-button-group` is the main container for grouped button elements.
Basic usage:
````csharp
<abp-button-group>
<abp-button button-type="Secondary">Left</abp-button>
<abp-button button-type="Secondary">Middle</abp-button>
<abp-button button-type="Secondary">Right</abp-button>
</abp-button-group>
````
## Demo
See the [button groups demo page](https://bootstrap-taghelpers.abp.io/Components/Button-groups) to see it in action.
## Attributes
### direction
A value indicates the direction of the buttons. Should be one of the following values:
* `Horizontal` (default value)
* `Vertical`
### size
A value indicates the size of the buttons in the group. Should be one of the following values:
* `Default` (default value)
* `Small`
* `Medium`
* `Large`

74
docs/en/UI/AspNetCore/Tag-Helpers/Carousel.md

@ -0,0 +1,74 @@
# Carousel
## Introduction
`abp-carousel` is a the abp tag for carousel element.
Basic usage:
````csharp
<abp-carousel>
<abp-carousel-item src=""></abp-carousel-item>
<abp-carousel-item src=""></abp-carousel-item>
<abp-carousel-item src=""></abp-carousel-item>
</abp-carousel>
````
## Demo
See the [carousel_demo page](https://bootstrap-taghelpers.abp.io/Components/Carousel) to see it in action.
## Attributes
### id
A value sets the id of the carousel. If not set, generated id will be set whenever the tag is created.
### controls
A value to enable the controls (previous and next buttons) on carousel. Should be one of the following values:
* `false`
* `true`
### indicators
A value to enables the indicators on carousel. Should be one of the following values:
* `false`
* `true`
### crossfade
A value to enables the fade animation instead of slide on carousel. Should be one of the following values:
* `false`
* `true`
## abp-carousel-item Attributes
### caption-title
A value sets the caption title of the carousel item.
### caption
A value sets the caption of the carousel item.
### src
A link value sets the source of the image displayed on carousel item.
### active
A value to set the active carousel item. Should be one of the following values:
* `false`
* `true`
### alt
A value sets the alternate text for the carousel item image when the image can not be displayed.

20
docs/en/docs-nav.json

@ -216,6 +216,10 @@
{
"text": "GUID Generation",
"path": "Guid-Generation.md"
},
{
"text": "Timing",
"path": "Timing.md"
}
]
},
@ -277,7 +281,8 @@
"path": "Application-Services.md"
},
{
"text": "Data Transfer Objects"
"text": "Data Transfer Objects",
"path": "Data-Transfer-Objects.md"
},
{
"text": "Unit Of Work"
@ -296,6 +301,15 @@
{
"text": "Dynamic C# API Clients",
"path": "API/Dynamic-CSharp-API-Clients.md"
},
{
"text": "ABP Endpoints",
"items": [
{
"text": "Application Configuration",
"path": "API/Application-Configuration.md"
}
]
}
]
},
@ -477,6 +491,10 @@
"path": "Dapper.md"
}
]
},
{
"text": "Data Seeding",
"path": "Data-Seeding.md"
}
]
},

22
docs/zh-Hans/API/Application-Configuration.md

@ -0,0 +1,22 @@
# 应用程序配置端点
ABP框架提供了一个预构建的标准端点,其中包含一些有关应用程序/服务的有用信息. 这里是此端点的一些基本信息的列表:
* [本地化](Localization.md)值, 支持应用程序的当前语言.
* 当前用户可用和已授予的[策略](Authorization.md)(权限).
* 当前用户的[设置](Settings.md)值.
* 关于[当前用户](CurrentUser.md)的信息 (如 id 和用户名).
* 关于当前[租户](Multi-Tenancy.md)的信息 (如 id 和名称).
* 当前用户的[时区](Timing.md)信息和应用程序的[时钟](Timing.md)类型.
## HTTP API
如果您导航到基于ABP框架的web应用程序或HTTP服务的 `/api/abp/application-configuration` URL, 你可以得到JSON对象形式配置. 该端点对于创建应用程序的客户端很有用.
## Script
对于ASP.NET Core MVC(剃刀页)应用程序,同样的配置值在JavaScript端也可用. `/Abp/ApplicationConfigurationScript` 是基于上述HTTP API自动生成的脚本的URL.
参阅 [JavaScript API文档](../UI/AspNetCore/JavaScript-API/Index.md) 了解关于ASP.NET Core UI.
其他UI类型提供相关平台的本地服务. 例如查看[Angular UI本地化文档](../UI/Angular/Localization.md)来学习如何使用这个端点公开的本地化值.

2
docs/zh-Hans/Application-Services.md

@ -2,7 +2,7 @@
应用服务实现应用程序的**用例**, 将**领域层逻辑公开给表示层**.
从表示层(可选)调用应用服务,**DTO (数据传对象)** 作为参数. 返回(可选)DTO给表示层.
从表示层(可选)调用应用服务,**DTO ([数据传对象](Data-Transfer-Objects.md))** 作为参数. 返回(可选)DTO给表示层.
## 示例

58
docs/zh-Hans/Blob-Storing-Azure.md

@ -0,0 +1,58 @@
# BLOB Storing Azure提供程序
BLOB存储Azure提供程序可以将BLOB存储在[Azure Blob storage](https://azure.microsoft.com/en-us/services/storage/blobs/)中.
> 阅读[BLOB存储文档](Blob-Storing.md)了解如何使用BLOB存储系统. 本文档仅介绍如何为容器配置Azure提供程序.
## 安装
使用ABP CLI添加[Volo.Abp.BlobStoring.Azure](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Azure)NuGet包到你的项目:
* 安装 [ABP CLI](https://docs.abp.io/en/abp/latest/CLI), 如果你还没有安装.
* 在要添加 `Volo.Abp.BlobStoring.Azure` 包的 `.csproj` 文件目录打开命令行.
* 运行 `Volo.Abp.BlobStoring.Azure` 命令.
如果要手动安装,在你的项目中安装 `Volo.Abp.BlobStoring.Azure` NuGet包然后将`[DependsOn(typeof(AbpBlobStoringAzureModule))]`添加到项目内的[ABP模块](Module-Development-Basics.md)类中.
## 配置
如同[BLOB存储文档](Blob-Storing.md)所述,配置是在[模块](Module-Development-Basics.md)类的 `ConfigureServices` 方法完成的.
**示例: 配置为默认使用Azure存储提供程序**
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containerscontainer.UseAzure(azure =>
{
azure.ConnectionString = "your azure connection string";
azure.ContainerName = "your azure container name";
azure.CreateContainerIfNotExists = false;
});
});
````
> 参阅[BLOB存储文档](Blob-Storing.md) 学习如何为指定容器配置提供程序.
### 选项
* **ConnectionString** (string): 连接字符串包括应用程序在运行时使用共享密钥授权访问Azure存储帐户中的数据所需的授权信息. 请参考[Azure文档](https://docs.microsoft.com/en-us/azure/storage/common/storage-configure-connection-string).
* **ContainerName** (string): 你可以在azure中指定容器名称. 如果没有指定它将使用 `BlogContainerName` 属性定义的BLOB容器的名称(请参阅[BLOB存储文档](Blob-Storing.md)). 请注意Azure有一些**命名容器的规则**,容器名称必须是有效的DNS名称,[符合以下命名规则](https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names):
* 容器名称必须以字母或数字开头或结尾,并且只能包含字母,数字和破折号(-)字符.
* 每个破折号(-)必须紧跟在字母或数字之后;容器名称中不允许使用连续的破折号.
* 容器名称中的所有字母都必须**小写**.
* 容器名称的长度必须在**3**到**63**个字符之间.
* **CreateContainerIfNotExists** (bool): 默认值为 `false`, 如果azure中不存在容器, `AzureBlobProvider` 将尝试创建它.
## Azure BLOB 名称计算器
Azure BLOB提供程序组织BLOB名称并实现一些约定. 默认情况下BLOB的全名由以下规则确定:
* 如果当前租户为 `null`(或容器禁用多租户 - 请参阅[BLOB存储文档](Blob-Storing.md) 了解如何禁用容器的多租户),则追加 `host` 字符串.
* 如果当前租户不为 `null`,则追加 `tenants/<tenant-id>` 字符串.
* 追加 BLOB 名称.
## 其他服务
* `AzureBlobProvider` 是实现Azure BLOB存储提供程序的主要服务,如果你想要通过[依赖注入](Dependency-Injection.md)覆盖/替换它(不要替换 `IBlobProvider` 接口,而是替换 `AzureBlobProvider` 类).
* `IAzureBlobNameCalculator` 服务用于计算文件路径. 默认实现是 `DefaultAzureBlobNameCalculator` . 如果你想自定义文件路径计算,可以替换/覆盖它.

177
docs/zh-Hans/Blob-Storing-Custom-Provider.md

@ -0,0 +1,177 @@
# BLOB 存储: 创建自定义提供程序
本文档通过一个示例说明如何为BLOB存储系统创建新的存储提供程序.
> 阅读[BLOB存储文档](Blob-Storing.md)了解如何使用BLOB存储系统. 本文档仅介绍如何创建新存储提供程序.
## 示例实现
第一步是创建一个实现 `IBlobProvider` 接口或 `BlobProviderBase` 抽象类继承的类.
````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` 继承 `BlobProviderBase` 并覆盖 `abstract` 方法. 实际的实现取决于你.
* 实现 `ITransientDependency` 接口将这个类注做为瞬态服务注册到[依赖注入](Dependency-Injection.md)系统.
> **注意: 命名约定很重要**. 如果类名没有以 `BlobProvider` 结尾,则必须手动注册/公开你的服务为 `IBlobProvider`.
这是所有. 现在你可以配置容器(在[模块](Module-Development-Basics.md)的 `ConfigureServices` 方法中)使用 `MyCustomBlobProvider` 类:
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.ProviderType = typeof(MyCustomBlobProvider);
});
});
````
> 如果你想配置特定的容器,请参阅[BLOB存储文档](Blob-Storing.md).
### BlobContainerConfiguration 扩展方法
如果你想提供一个更简单的配置方式,可以为 `BlobContainerConfiguration` 类创建一个扩展方法:
````csharp
public static class MyBlobContainerConfigurationExtensions
{
public static BlobContainerConfiguration UseMyCustomBlobProvider(
this BlobContainerConfiguration containerConfiguration)
{
containerConfiguration.ProviderType = typeof(MyCustomBlobProvider);
return containerConfiguration;
}
}
````
然后你可以使用扩展方法更容易地配置容器:
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.UseMyCustomBlobProvider();
});
});
````
### 额外的配置选项
`BlobContainerConfiguration` 允许添加/删除提供程序特定的配置对象. 如果你的提供者需要额外的配置,你可以为 `BlobContainerConfiguration` 创建一个包装类提供的类型安全配置选项:
````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;
}
}
````
然后你可以这样更改 `MyBlobContainerConfigurationExtensions` 类:
````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);
}
}
````
* 向 `UseMyCustomBlobProvider` 方法添加了一个参数,允许开发人员设置其他选项.
* 添加了一个新的 `GetMyCustomBlobProviderConfiguration` 方法,该方法将在 `MyCustomBlobProvider` 类内使用获取配置的值.
然后任何人都可以如下设置 `MyOption1`:
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.UseMyCustomBlobProvider(provider =>
{
provider.MyOption1 = "my value";
});
});
});
````
最后你可以使用 `GetMyCustomBlobProviderConfiguration` 方法访问额外的选项:
````csharp
public class MyCustomBlobProvider : BlobProviderBase, ITransientDependency
{
public override Task SaveAsync(BlobProviderSaveArgs args)
{
var config = args.Configuration.GetMyCustomBlobProviderConfiguration();
var value = config.MyOption1;
//...
}
}
````
## 贡献?
如果你创建了一个新的提供程序,并且认为它对其他开发者有用,请考虑为GitHub上的ABP框架做出[贡献](Contribution/Index.md).

96
docs/zh-Hans/Blob-Storing-Database.md

@ -0,0 +1,96 @@
# BLOB存储数据库提供程序
BLOB存储数据库提供程序可以将BLOB存储在关系或非关系数据库中.
有两个数据库提供程序实现;
* [Volo.Abp.BlobStoring.Database.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.EntityFrameworkCore) 包实现[EF Core](Entity-Framework-Core.md), 它可以通过EF Core存储BLOB在[任何支持的DBMS](https://docs.microsoft.com/en-us/ef/core/providers/)中.
* [Volo.Abp.BlobStoring.Database.MongoDB](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.MongoDB) 包实现了[MongoDB](MongoDB.md).
> 阅读[BLOB存储文档](Blob-Storing.md)了解如何使用BLOB存储系统. 本文档仅介绍如何为容器配置数据库提供程序.
## 安装
### 自动安装
如果你已基于[应用程序启动模板](Startup-Templates/Application.md)创建了解决方案,则可以使用 `abp add-module` [CLI](CLI.md)命令将相关软件包自动添加到解决方案中.
在包含解决方案(`.sln`)文件的文件夹中打开命令行运行以下命令:
````bash
abp add-module Volo.Abp.BlobStoring.Database
````
此命令将所有NuGet软件包添加到解决方案的相应层. 如果使用的是EF Core,它会添加必要的配置,添加新的数据库迁移并更新数据库.
### 手动安装
这里是此提供程序定义的所有包:
* [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)
你可以只安装 `Volo.Abp.BlobStoring.Database.EntityFrameworkCore``Volo.Abp.BlobStoring.Database.MongoDB` (根据你的偏好),因为它们依赖其他包.
安装完成后,添加 `DepenedsOn` 属性到相关[模块](Module-Development-Basics.md).下面是由上面列出的相关NuGet包定义的模块类列表:
* `BlobStoringDatabaseDomainModule`
* `BlobStoringDatabaseDomainSharedModule`
* `BlobStoringDatabaseEntityFrameworkCoreModule`
* `BlobStoringDatabaseMongoDbModule`
如果你正在使用EF Core,还需要配置你的**Migration DbContext**将BLOB存储表添加到你的数据库. 在 `OnModelCreating` 方法中调用 `builder.ConfigureBlobStoring()` 扩展方法来包含到DbContext的映射. 你可以使用标准的 `Add-Migration``Update-Database` [命令](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/)在数据库中创建必要的表.
## 配置
### 连接字符串
如果你要使用你的 `Default` 连接字符串,则不需要做任何其他配置.
如果要将BLOB存储到单独的数据库,请在配置文件(`appsettings.json`)中将 `AbpBlobStoring` 用作连接字符串名称. 请阅读[EF Core Migrations](Entity-Framework-Core-Migrations.md)文档了解如何为所需模块创建和使用其他数据库.
### 配置容器
如果只使用数据库存储提供程序,则不需要手动配置,因为它是自动完成的. 如果使用多个存储提供程序,可能需要对其进行配置.
如同[BLOB存储文档](Blob-Storing.md)所述,配置是在[模块](Module-Development-Basics.md)类的 `ConfigureServices` 方法完成的.
**示例: 配置为默认使用数据库系统存储提供程序**
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.UseDatabase();
});
});
````
> 参阅[BLOB存储文档](Blob-Storing.md) 学习如何为指定容器配置提供程序.
## 附加信息
它需要使用[BLOB存储服务](Blob-Storing.md)来使用BLOB存储系统. 但是如果要处理数据库表/实体,可以使用以下信息.
### 实体
此模块定义的实体:
* `DatabaseBlobContainer` (aggregate root) 表示存储在数据库中的容器.
* `DatabaseBlob` (aggregate root) 表示数据库中的BLOB.
参阅[实体文档](Entities.md)了解什么是实体和聚合根.
### 仓储
* `IDatabaseBlobContainerRepository`
* `IDatabaseBlobRepository`
你还可以使用 `IRepository``IRepository` 来获得 `IQueryable` 能力. 更多信息请参阅[仓储文档](Repositories.md).
### 其他服务
* `DatabaseBlobProvider` 是实现数据库BLOB存储提供程序的主要服务,如果你想要通过[依赖注入](Dependency-Injection.md)覆盖/替换它(不要替换 `IBlobProvider` 接口,而是替换 `DatabaseBlobProvider` 类).

58
docs/zh-Hans/Blob-Storing-File-System.md

@ -0,0 +1,58 @@
# BLOB存储文件系统提供程序
文件系统存储提供程序用于将BLOB作为文件夹中的标准文件存储在本地文件系统中.
> 阅读[BLOB存储文档](Blob-Storing.md)了解如何使用BLOB存储系统. 本文档仅介绍如何为容器配置文件系统.
## 介绍
使用ABP CLI添加[Volo.Abp.BlobStoring.FileSystem](https://www.nuget.org/packages/Volo.Abp.BlobStoring.FileSystem)NuGet包到你的项目:
* 安装 [ABP CLI](https://docs.abp.io/en/abp/latest/CLI), 如果你还没有安装.
* 在要添加 `Volo.Abp.BlobStoring.FileSystem` 包的 `.csproj` 文件目录打开命令行.
* 运行 `abp add-package Volo.Abp.BlobStoring.FileSystem` 命令.
如果要手动安装,在你的项目中安装 `Volo.Abp.BlobStoring.FileSystem` NuGet包然后将`[DependsOn(typeof(AbpBlobStoringFileSystemModule))]`添加到项目内的[ABP模块](Module-Development-Basics.md)类中.
## 配置
如同[BLOB存储文档](Blob-Storing.md)所述,配置是在[模块](Module-Development-Basics.md)类的 `ConfigureServices` 方法完成的.
**示例: 配置为默认使用文件系统存储提供程序**
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.UseFileSystem(fileSystem =>
{
fileSystem.BasePath = "C:\\my-files";
});
});
});
````
`UseFileSystem` 扩展方法用于为容器设置文件系统提供程序并配置文件系统选项.
> 参阅[BLOB存储文档](Blob-Storing.md) 学习如何为指定容器配置提供程序.
### 选项
* **BasePath** (string): 存储BLOB的基本文件夹路径,它是必选的.
* **AppendContainerNameToBasePath** (bool; 默认: `true`): 指定是否在基本文件夹中创建具有容器名称的文件夹. 如果你在同一个 `BaseFolder` 中存储多个容器,请将其保留为`true`. 如果你不喜欢不必要的更深层次的文件夹,你可以将它设置为 `false`.
## 文件路径计算
文件系统提供程序在文件夹中组织BLOB文件并实现一些约定. 默认情况下,BLOB文件的完整路径由以下规则确定:
* 它以如上所述配置的 `BasePath` 开始.
* 如果当前租户为 `null`(或容器禁用多租户 - 请参阅[BLOB存储文档](Blob-Storing.md) 了解如何禁用容器的多租户),则追加 `host` 文件夹.
* 如果当前租户不为 `null`,则追加 `tenants/<tenant-id>` 文件夹.
* 如果 `AppendContainerNameToBasePath` 为`true`,则追加容器的名称. 如果容器名称包含 `/`,将导致文件夹嵌套.
* 追加BLOB名称,如果BLOB名称包含 `/` 它创建文件夹. 如果BLOB名称包含 `.` 它将有一个文件扩展名.
## 扩展文件系统提供程序
* `FileSystemBlobProvider` 是实现文件系统存储的主要服务. 你可以从这个类继承并[覆盖](Customizing-Application-Modules-Overriding-Services.md)方法进行自定义.
* `IBlobFilePathCalculator` 服务用于计算文件路径. 默认实现是 `DefaultBlobFilePathCalculator` . 如果你想自定义文件路径计算,可以替换/覆盖它.

306
docs/zh-Hans/Blob-Storing.md

@ -0,0 +1,306 @@
# BLOB 存储
通常将文件内容存储在应用程序中并根据需要读取这些文件内容. 不仅是文件你可能还需要将各种类型的[BLOB](https://en.wikipedia.org/wiki/Binary_large_object)(大型二进制对象)保存到存储中. 例如你可能要保存用户个人资料图片.
BLOB通常是一个**字节数组**. 有很多地方可以存储BLOB项. 可以选择将其存储在本地文件系统中,共享数据库中或[Azure BLOB存储](https://azure.microsoft.com/zh-cn/services/storage/blobs/)中.
ABP框架为BLOB提供了抽象,并提供了一些可以轻松集成到的预构建存储提供程序. 抽象有一些好处;
* 你可以通过几行配置**轻松的集成**你喜欢的BLOB存储提供程序.
* 你可以**轻松的更改**BLOB存储,而不用改变你的应用程序代码.
* 如果你想创建**可重用的应用程序模块**,无需假设BLOB的存储方式.
ABP BLOG存储系统兼容ABP框架其他功能,如[多租户](Multi-Tenancy.md).
## BLOB 存储提供程序
ABP框架已经有以下存储提供程序的实现;
* [File System](Blob-Storing-File-System.md):将BLOB作为标准文件存储在本地文件系统的文件夹中.
* [Database](Blob-Storing-Database.md): 将BLOB存储在数据库中.
* [Azure](Blob-Storing-Azure.md): 将BLOG存储在 [Azure BLOB storage](https://azure.microsoft.com/en-us/services/storage/blobs/)中.
以后会实现更多的提供程序,你可以为自己喜欢的提供程序创建[请求](https://github.com/abpframework/abp/issues/new),或者你也可以[自己实现](Blob-Storing-Custom-Provider.md)它并[贡献](Contribution/Index.md)到ABP框架.
可以在**容器系统**的帮助下一起**使用多个提供程序**,其中每个容器可以使用不同的提供程序.
> 除非你**配置存储提供程序**否则BLOB存储系统无法工作. 有关存储提供程序配置请参考链接的文档.
## 安装
[Volo.Abp.BlobStoring](https://www.nuget.org/packages/Volo.Abp.BlobStoring)是定义BLOB存储服务的主要包. 你可以用此包使用BLOB存储系统而不依赖特定存储提供程序.
使用ABP CLI这个包添加到你的项目:
* 安装 [ABP CLI](https://docs.abp.io/en/abp/latest/CLI), 如果你还没有安装.
* 在要添加 `Volo.Abp.BlobStoring` 包的 `.csproj` 文件目录打开命令行.
* 运行 `abp add-package Volo.Abp.BlobStoring` 命令.
如果要手动安装,在你的项目中安装 `Volo.Abp.BlobStoring` NuGet包然后将`[DependsOn(typeof(AbpBlobStoringModule))]`添加到项目内的[ABP模块](Module-Development-Basics.md)类中.
## IBlobContainer
`IBlobContainer` 是存储和读取BLOB的主要接口. 应用程序可能有多个容器,每个容器都可以单独配置. 有一个**默认容器**可以通过注入 `IBlobContainer` 来简单使用.
**示例: 简单地保存和读取命名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");
}
}
}
````
该服务用 `my-blob-1` 名称保存给定的字节,然后以相同的名称获取先前保存的字节.
> 一个BLOB是一个命名对象,**每个BLOB都应该有一个唯一的名称**,它是一个任意的字符串.
`IBlobContainer` 可以处理 `Stream``byte[]` 对象,在下一节中将详细介绍.
### 保存 BLOB
`SaveAsync` 方法用于保存新的或替换现有的BLOB. 默认情况下,它可以保存 `Stream`,但是有一个快捷的扩展方法来保存字节数组.
`SaveAsync` 有以下参数:
* **name** (string): 唯一的BLOB名称.
* **stream** (Stream) or **byte** (byte[]): 读取BLOB内容或字节数组的流.
* **overrideExisting** (bool): 设置为 `true`,如果BLOB内容已经存在,则替换它. 默认值为 `false`,则抛出 `BlobAlreadyExistsException` 异常.
### 读取/获取 BLOB
* `GetAsync`: 返回给定BLOB名称可用于读取BLOB内容的 `Stream` 对象. 使用后始终要**dispose流**. 如果找不到具有给定名称的BLOB,则抛出异常.
* `GetOrNullAsync`: 与 `GetAsync` 方法相反,如果未找到给定名称的BLOG,则返回 `null`.
* `GetAllBytesAsync`: 返回 `byte[]` 而不是 `Stream`. 如果找不到具有给定名称的BLOB,则抛出异常.
* `GetAllBytesOrNullAsync`: 与 `GetAllBytesAsync` 方法相反,如果未找到给定名称的BLOG,则返回 `null`.
### 删除 BLOB
`DeleteAsync` 使用给定BLOB名称删除BLOB数据. 如果找不到给定的BLOB不会引发任何异常. 相反如果你关心BLOB,它会返回一个 `bool`,表示BLOB实际上是否已删除.
### 其他方法
* `ExistsAsync` 方法简单的检查容器中是否存在具有给定名称的BLOB.
### 关于命名BLOB
没有命名BLOB的规则. BLOB名称只是每个容器(和每个租户-参见"*多租户*"部分)唯一的字符串. 但是不同的存储提供程序可能会按惯例实施某些做法. 例如[文件系统提供程序](Blob-Storing-File-System.md)在BLOB名称中使用目录分隔符 (`/`) 和文件扩展名(如果BLOB名称为 `images/common/x.png` ,则在根容器文件夹下的 `images/common` 文件夹中存储 `x.png`).
## 类型化 IBlobContainer
类型化BLOB容器系统是一种在应用程序中创建和管理**多个容器**的方法;
* **每个容器分别存储**. 这意味着BLOB名称在一个容器中应该是唯一的,两个具有相同名称的BLOB可以存在不同的容器中不会互相影响.
* **每个容器可以单独配置**,因此每个容器可以根据你的配置使用不同的存储提供程序.
要创建类型化容器,需要创建一个简单的用 `BlobContainerName` 属性装饰的类:
````csharp
using Volo.Abp.BlobStoring;
namespace AbpDemo
{
[BlobContainerName("profile-pictures")]
public class ProfilePictureContainer
{
}
}
````
> 如果不使用 `BlobContainerName` attribute,ABP Framework将使用类的全名(带有名称空间),但是始终建议使用稳定的容器名称,即使重命名该类也不会被更改.
创建容器类后,可以为容器类型注入 `IBlobContainer<T>`.
**示例: 用于保存和读取[当前用户](CurrentUser.md)的个人资料图片的[应用服务](Application-Services.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>` 有与 `IBlobContainer` 相同的方法.
> 在开发可重复使用的模块时,**始终使用类型化的容器是一个好习惯**,这样最终的应用程序就可以为你的容器配置提供程序,而不会影响其他容器.
### 默认容器
如果不使用泛型参数,直接注入 `IBlobContainer` (如上所述),会得到默认容器. 注入默认容器的另一种方法是使用 `IBlobContainer<DefaultContainer>`,它返回完全相同的容器.
默认容器的名称是 `Default`.
### 命令容器
类型容器只是命名容器的快捷方式. 你可以注入并使用 `IBlobContainerFactory` 来获得一个BLOB容器的名称:
````csharp
public class ProfileAppService : ApplicationService
{
private readonly IBlobContainer _blobContainer;
public ProfileAppService(IBlobContainerFactory blobContainerFactory)
{
_blobContainer = blobContainerFactory.Create("profile-pictures");
}
//...
}
````
## IBlobContainerFactory
`IBlobContainerFactory` 是用于创建BLOB容器的服务. 上面提供了一个示例.
**示例: 通过名称创建容器**
````csharp
var blobContainer = blobContainerFactory.Create("profile-pictures");
````
**示例: 通过类型创建容器**
````csharp
var blobContainer = blobContainerFactory.Create<ProfilePictureContainer>();
````
> 通常你不需要使用 `IBlobContainerFactory`, 因为在注入 `IBlobContainer` 或`IBlobContainer<T>` 时会在内部使用它.
## 配置容器
在使用容器之前应先对其进行配置. 最基本的配置是选择一个 **BLOB存储提供程序**(请参阅上面的"*BLOB存储提供程序*"部分).
`AbpBlobStoringOptions` 是用于配置容器的[选项类](Options.md). 你可以在[模块](Module-Development-Basics.md)的 `ConfigureServices` 方法中配置选项.
### 配置单个容器
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.Configure<ProfilePictureContainer>(container =>
{
//TODO...
});
});
````
这个例子配置 `ProfilePictureContainer`. 你还可以通过容器名称进行配置:
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.Configure("profile-pictures", container =>
{
//TODO...
});
});
````
### 配置默认容器
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
//TODO...
});
});
````
> 默认容器有一个特殊情况;如果不为容器指定配置,则**返回到默认容器配置**. 这是一种为所有容器配置默认值并在需要时专门针对特定容器进行配置的好方法.
### 配置所有容器
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureAll((containerName, containerConfiguration) =>
{
//TODO...
});
});
````
这是配置所有容器的方式.
> 与配置默认容器的主要区别在于, `ConfigureAll` 会覆盖配置,即使它是专门为特定容器配置的.
## 多租户
如果你的应用程序是多租户的,BLOB存储系统可以**与[多租户](Multi-Tenancy.md)无缝协作**. 所有提供程序都将多租户实现为标准功能. 它们将不同租户的**BLOB彼此隔离**,因此它们只能访问自己的BLOB. 这意味着你可以**为不同的租户使用相同的BLOB名称**.
如果应用程序是多租户的,则可能需要单独控制容器的**多租户行为**. 例如你可能希望**禁用特定容器的多租户**,这样容器中的BLOB将对**所有租户可用**. 这是在所有租户之间共享BLOB的一种方法.
**示例: 禁用特定容器的多租户**
````csharp
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.Configure<ProfilePictureContainer>(container =>
{
container.IsMultiTenant = false;
});
});
````
> 如果你的应用程序不是多租户的,不用担心,它会正常工作. 你不需要配置 `IsMultiTenant` 选项.
## 扩展BLOB存储系统
大多数时候除了创建定制的BLOB存储提供程序外,你不需要[自定义BLOB存储系统](Blob-Storing-Custom-Provider.md).但是如果需要,你可以替换任何服务(通过[依赖注入](Dependency-Injection.md)). 这里有一些上面没有提到的其他服务,但你可能想知道:
* `IBlobProviderSelector` 用于通过容器名称获取 `IBlobProvider` 实例. 默认实现(`DefaultBlobProviderSelector`)使用配置选择提供程序.
* `IBlobContainerConfigurationProvider` 用于获取给定容器名称的`BlobContainerConfiguration`. 默认实现(`DefaultBlobContainerConfigurationProvider`)从上述 `AbpBlobStoringOptions` 获取配置.
## BLOB 存储 vs 文件管理系统
注意BLOB存储不是一个文件管理系统. 它是一个用于保存,获取和删除命名BLOG的低级别系统. 它不提供目录那样的层次结构,这是典型文件系统所期望的.
如果你想创建文件夹并在文件夹之间移动文件,为文件分配权限并在用户之间共享文件,那么你需要在BLOB存储系统上实现你自己的应用程序.
## 另请参阅
* [创建自定义BLOB存储提供程序](Blob-Storing-Custom-Provider.md)

1
docs/zh-Hans/CLI.md

@ -87,6 +87,7 @@ abp new Acme.BookStore
* `mongodb`: MongoDB.
* `module`: [Module template](Startup-Templates/Module.md). 其他选项:
* `--no-ui`: 不包含UI.仅创建服务模块(也称为微服务 - 没有UI).
* **`console`**: [Console template](Startup-Templates/Console.md).
* `--output-folder` 或者 `-o`: 指定输出文件夹,默认是当前目录.
* `--version` 或者 `-v`: 指定ABP和模板的版本.它可以是 [release tag](https://github.com/abpframework/abp/releases) 或者 [branch name](https://github.com/abpframework/abp/branches). 如果没有指定,则使用最新版本.大多数情况下,你会希望使用最新的版本.
* `--template-source` 或者 `-ts`: 指定自定义模板源用于生成项目,可以使用本地源和网络源(例如 `D\localTemplate``https://<your url>.zip`).

14
docs/zh-Hans/Connection-Strings.md

@ -73,8 +73,18 @@ public class IdentityServerDbContext
启动模板(使用 EF Core ORM) 带有一个数据库和一个 `.EntityFrameworkCore.DbMigrations` 项目,其中包含数据库的迁移文件. 该项目主要定义了一个*YourProjectName*MigrationsDbContext,它调用所有模块的 `Configure...()` 方法,例如 `builder.ConfigurePermissionManagement()`.
一旦要分离模块的数据库,通常需要创建第二个迁移路径. 最简单的方法是创建一个带有 `DbContext``.EntityFrameworkCore.DbMigrations` 项目副本, 更改为只调用需要存储在第二个数据库中的模块的 `Configure...()` 方法并重新创建迁移. 这时你还需要更改 `.DbMigrator` 应用程序使其兼容第二个数据库,这样每个数据库将有一个单独的迁移DbContext.
一旦要分离模块的数据库,通常需要创建第二个迁移路径. 请参阅[EF Core迁移文档](Entity-Framework-Core-Migrations.md)了解如何为所需模块创建和使用其他数据库.
## 多租户
参阅 [多租户文档](Multi-Tenancy.md)了解如何为租户使用单独的数据库.
参阅 [多租户文档](Multi-Tenancy.md)了解如何为租户使用单独的数据库.
## 替换连接字符串解析器
ABP定义了 `IConnectionStringResolver`,并在需要连接字符串时使用它. 有两个预构建的实现:
* `DefaultConnectionStringResolver` 根据上面"配置连接字符串"一节中定义的规则,使用 `AbpDbConnectionOptions` 选择连接字符串.
* `MultiTenantConnectionStringResolver` used for multi-tenant applications and tries to get the configured connection string for the current tenant if available. It uses the `ITenantStore` to find the connection strings. It inherits from the `DefaultConnectionStringResolver` and fallbacks to the base logic if no connection string specified for the current tenant.
* `MultiTenantConnectionStringResolver` 用于多租户应用程序,并尝试获取当前租户的已配置连接字符串(如果有). 它使用 `ITenantStore` 查找连接字符串. 它继承了 `DefaultConnectionStringResolver`, 如果没有为当前租户指定连接字符串则回退到基本逻辑.
如果需要自定义逻辑来确定连接字符串,可以实现 `IConnectionStringResolver` 接口(也可以从现有类派生)并使用[依赖注入](Dependency-Injection.md)系统替换现有实现.

168
docs/zh-Hans/CurrentUser.md

@ -1,3 +1,167 @@
# Current User
# 当前用户
TODO!
在Web应用程序中检索有关已登录用户的信息是很常见的. 当前用户是与Web应用程序中的当前请求相关的活动用户.
## ICurrentUser
`ICurrentUser` 是主要的服务,用于获取有关当前活动的用户信息.
示例: [注入](Dependency-Injection.md) `ICurrentUser` 到服务中:
````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;
}
}
}
````
公共基类已经将此服务作为基本属性注入. 例如你可以直接在[应用服务](Application-Services.md)中使用 `CurrentUser` 属性:
````csharp
using System;
using Volo.Abp.Application.Services;
namespace AbpDemo
{
public class MyAppService : ApplicationService
{
public void Foo()
{
Guid? userId = CurrentUser.Id;
}
}
}
````
### 属性
以下是 `ICurrentUser` 接口的基本属性:
* **IsAuthenticated** 如果当前用户已登录(已认证),则返回 `true`. 如果用户尚未登录,则 `Id``UserName` 将返回 `null`.
* **Id** (Guid?): 当前用户的Id,如果用户未登录,返回 `null`.
* **UserName** (string): 当前用户的用户名称. 如果用户未登录,返回 `null`.
* **TenantId** (Guid?): 当前用户的租户Id. 对于[多租户](Multi-Tenancy.md) 应用程序很有用. 如果当前用户未分配给租户,返回 `null`.
* **Email** (string): 当前用户的电子邮件地址. 如果当前用户尚未登录或未设置电子邮件地址,返回 `null`.
* **EmailVerified** (bool): 如果当前用户的电子邮件地址已经过验证,返回 `true`.
* **PhoneNumber** (string): 当前用户的电话号码. 如果当前用户尚未登录或未设置电话号码,返回 `null`.
* **PhoneNumberVerified** (bool): 如果当前用户的电话号码已经过验证,返回 `true`.
* **Roles** (string[]): 当前用户的角色. 返回当前用户角色名称的字符串数组.
### Methods
`ICurrentUser` 是在 `ICurrentPrincipalAccessor` 上实现的(请参阅以下部分),并可以处理声明. 实际上所有上述属性都是从当前经过身份验证的用户的声明中检索的.
如果你有自定义声明或获取其他非常见声明类型, `ICurrentUser` 有一些直接使用声明的方法.
* **FindClaim**: 获取给定名称的声明,如果未找到返回 `null`.
* **FindClaims**: 获取具有给定名称的所有声明(允许具有相同名称的多个声明值).
* **GetAllClaims**: 获取所有声明.
* **IsInRole**: 一种检查当前用户是否在指定角色中的简化方法.
除了这些标准方法,还有一些扩展方法:
* **FindClaimValue**: 获取具有给定名称的声明的值,如果未找到返回 `null`. 它有一个泛型重载将值强制转换为特定类型.
* **GetId**: 返回当前用户的 `Id`. 如果当前用户没有登录它会抛出一个异常(而不是返回`null`). 仅在你确定用户已经在你的代码上下文中进行了身份验证时才使用此选项.
### 验证和授权
`ICurrentUser` 的工作方式与用户的身份验证或授权方式无关. 它可以与使用当前主体的任何身份验证系统无缝地配合使用(请参阅下面的部分).
## ICurrentPrincipalAccessor
`ICurrentPrincipalAccessor` 是当需要当前用户的principle时使用的服务(由ABP框架和你的应用程序代码使用).
对于Web应用程序, 它获取当前 `HttpContext``User` 属性,对于非Web应用程序它将返回 `Thread.CurrentPrincipal`.
> 通常你不需要这种低级别的 `ICurrentPrincipalAccessor` 服务,直接使用上述的 `ICurrentUser` 即可.
### 基本用法
你可以注入 `ICurrentPrincipalAccessor` 并且使用 `Principal` 属性获取当前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();
//...
}
}
````
### 更改当前Principle
除了某些高级场景外,你不需要设置或更改当前principle. 如果需要可以使用 `ICurrentPrincipalAccessor``Change` 方法. 它接受一个 `ClaimsPrinciple` 对象并使其成为作用域的"当前"对象.
示例:
````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"
//...
}
}
}
````
始终在 `using` 语句中使用 `Change` 方法,在 `using` 范围结束后它将恢复为原始值.
这可以是一种模拟用户登录的应用程序代码范围的方法,但是请尝试谨慎使用它.
## AbpClaimTypes
`AbpClaimTypes` 是一个静态类它定义了标准声明的名称被ABP框架使用.
* `UserName`, `UserId`, `Role``Email` 属性的默认值是通常[System.Security.Claims.ClaimTypes](https://docs.microsoft.com/en-us/dotnet/api/system.security.claims.claimtypes)类设置的, 但你可以改变它们.
* 其他属性,如 `EmailVerified`, `PhoneNumber`, `TenantId` ...是由ABP框架通过尽可能遵循标准名称来定义的.
建议使用这个类的属性来代替声明名称的魔术字符串.

161
docs/zh-Hans/Data-Seeding.md

@ -1,3 +1,160 @@
# Data Seeding
# 种子数据
TODO
## 介绍
使用数据库的某些应用程序(或模块),可能需要有一些**初始数据**才能​​够正常启动和运行. 例如**管理员用户**和角色必须在一开始就可用. 否则你就无法**登录**到应用程序创建新用户和角色.
数据种子也可用于[测试](Testing.md)的目的,你的自动测试可以假定数据库中有一些可用的初始数据.
### 为什么要有种子数据系统?
尽管EF Core Data Seeding系统提供了一种方法,但它非常有限,不包括生产场景. 此外它仅适用于EF Core.
ABP框架提供了种子数据系统;
* **模块化**: 任何[模块](Module-Development-Basics.md)都可以无声地参与数据播种过程,而不相互了解和影响. 通过这种方式模块将种子化自己的初始数据.
* **数据库独立**: 它不仅适用于 EF Core, 也使用其他数据库提供程序(如 [MongoDB](MongoDB.md)).
* **生产准备**: 它解决了生产环境中的问题. 参见下面的*On Production*部分.
* **依赖注入**: 它充分利用了依赖项注入,你可以在播种初始数据时使用任何内部或外部服务. 实际上你可以做的不仅仅是数据播种.
## IDataSeedContributor
将数据种子化到数据库需要实现 `IDataSeedContributor` 接口.
**示例: 如果没有图书,则向数据库播种一个初始图书**
````csharp
using System;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Guids;
namespace Acme.BookStore
{
public class BookStoreDataSeedContributor
: IDataSeedContributor, ITransientDependency
{
private readonly IRepository<Book, Guid> _bookRepository;
private readonly IGuidGenerator _guidGenerator;
public BookStoreDataSeedContributor(
IRepository<Book, Guid> bookRepository,
IGuidGenerator guidGenerator)
{
_bookRepository = bookRepository;
_guidGenerator = guidGenerator;
}
public async Task SeedAsync(DataSeedContext context)
{
if (await _bookRepository.GetCountAsync() > 0)
{
return;
}
var book = new Book(
id: _guidGenerator.Create(),
name: "The Hitchhiker's Guide to the Galaxy",
type: BookType.ScienceFiction,
publishDate: new DateTime(1979, 10, 12),
price: 42
);
await _bookRepository.InsertAsync(book);
}
}
}
````
* `IDataSeedContributor` 定义了 `SeedAsync` 方法用于执行 **数据种子逻辑**.
* 通常**检查数据库**是否已经存在种子数据.
* 你可以**注入**服务,检查数据播种所需的任何逻辑.
> 数据种子贡献者由ABP框架自动发现,并作为数据播种过程的一部分执行.
### DataSeedContext
如果你的应用程序是[多租户](Multi-Tenancy.md), `DataSeedContext` 包含 `TenantId`,因此你可以在插入数据或基于租户执行自定义逻辑时使用该值.
`DataSeedContext` 还包含用于从 `IDataSeeder` 传递到种子提供者的name-value配置参数.
## 模块化
一个应用程序可以具有多个种子数据贡献者(`IDataSeedContributor`)类. 任何可重用模块也可以实现此接口播种其自己的初始数据.
例如[Identity模块](Modules/Identity.md)有一个种子数据贡献者,它创建一个管理角色和管理用户并分配所有权限.
## IDataSeeder
> 通常你不需要直接使用 `IDataSeeder` 服务,因为如果你从[应用程序启动模板](Startup-Templates/Application.md)开始,该服务已经完成. 但是建议阅读以了解种子数据系统背后的设计.
`IDataSeeder` 是用于生成初始数据的主要服务. 使用它很容易;
````csharp
public class MyService : ITransientDependency
{
private readonly IDataSeeder _dataSeeder;
public MyService(IDataSeeder dataSeeder)
{
_dataSeeder = dataSeeder;
}
public async Task FooAsync()
{
await _dataSeeder.SeedAsync();
}
}
````
你可以[注入](Dependency-Injection.md) `IDataSeeder` 并且在你需要时使用它初始化种子数据. 它内部调用 `IDataSeedContributor` 的实现去完成数据播种.
可以将命名的配置参数发送到 `SeedAsync` 方法,如下所示:
````csharp
await _dataSeeder.SeedAsync(
new DataSeedContext()
.WithProperty("MyProperty1", "MyValue1")
.WithProperty("MyProperty2", 42)
);
````
然后种子数据提供者可以通过前面解释的 `DataSeedContext` 访问这些属性.
如果模块需要参数,应该在[模块文档](Modules/Index.md)中声明它. 例如[Identity Module](Modules/Identity.md)使用 `AdminEmail``AdminPassword` 参数,如果你提供了(默认使用默认值).
### 在何处以及如何播种数据?
重要的是要了解在何处以及如何执行 `IDataSeeder.SeedAsync()`.
#### On Production
[应用程序启动模板](Startup-Templates/Application.md)带有一个*YourProjectName***.DbMigrator** 项目(图中的Acme.BookStore.DbMigrator). 这是一个**控制台应用程序**,负责**迁移**数据库架构(关系数据库)和初始种子数据:
![bookstore-visual-studio-solution-v3](images/bookstore-visual-studio-solution-v3.png)
控制台应用程序已经为你正确配置,它甚至支持**多租户**场景,其中每个租户拥有自己的数据库(迁移和必须的数据库).
当你将解决方案的**新版本部署到服务器**时,都需要运行这个DbMigrator应用程序. 它会迁移你的**数据库架构**(创建新的表/字段…)和播种正确运行解决方案的新版本所需的**新初始数据**. 然后就可以部署/启动实际的应用程序了.
即使你使用的是MongoDB或其他NoSQL数据库(不需要进行架构迁移),也建议使用DbMigrator应用程序为你的数据添加种子或执行数据迁移.
有这样一个单独的控制台应用程序有几个优点;
* 你可以在更新你的应用程序**之前运行它**,所以你的应用程序可以在准备就绪的数据库上运行.
* 与本身初始化种子数据相比,你的应用程序**启动速度更快**.
* 应用程序可以在**集群环境**中正确运行(其中应用程序的多个实例并发运行). 在这种情况下如果在应用程序启动时播种数据就会有冲突.
#### On Development
我们建议以相同的方式进行开发. 每当你[创建数据库迁移](https://docs.microsoft.com/en-us/ef/ef6/modeling/code-first/migrations/)(例如使用EF Core `Add-Migration` 命令)或更改数据种子代码(稍后说明)时,请运行DbMigrator控制台应用程序.
> 你可以使用EF Core继续执行标准的 `Update-Database` 命令,但是它不会初始化种子数据.
#### On Testing
你可能想为自动[测试](Testing.md)初始化数据种子, 这需要使用 `IDataSeeder.SeedAsync()`. 在[应用程序启动模板](Startup-Templates/Application.md)中,它在TestBase项目的*YourProjectName*TestBaseModule类的[OnApplicationInitialization](Module-Development-Basics.md)方法中完成.
除了标准种子数据(也在生产中使用)之外,你可能还希望为自动测试添加其他种子数据. 你可以在测试项目中创建一个新的数据种子贡献者以处理更多数据.

281
docs/zh-Hans/Data-Transfer-Objects.md

@ -1,3 +1,280 @@
## Data Transfer Objects
# 数据传输对象
TODO
## 介绍
**数据传输对象**(DTO)用于在**应用层**和**表示层**或其他类型的客户端之间传输数据.
通常用**DTO**作为参数在表示层(可选)调用[应用服务](Application-Services.md). 它使用领域对象执行某些**特定的业务逻辑**,并(可选)将DTO返回到表示层.因此表示层与领域层完全**隔离**.
### DTO的需求
> 如果你感觉你已经知道并确认使用DTO的好处,你可以**跳过这一节**.
首先为每个应用程序服务方法创建DTO类可能被看作是一项冗长而耗时的工作. 但是如果正确使用它们,它们可以保存在应用程序. 为什么和如何>
#### 领域层的抽象
DTO提供了一种从表示层**抽象领域对象**的有效方法. 实际上你的**层**被正确地分开了. 如果希望完全更改表示层,可以继续使用现有的应用程序层和领域层. 或者你可以重写领域层完全更改数据库架构,实体和O/RM框架,而无需更改表示层. 当然前提是应用程序服务的契约(方法签名和dto)保持不变.
#### 数据隐藏
假设你有一个具有属性Id,名称,电子邮件地址和密码的 `User` 实体. 如果 `UserAppService``GetAllUsers()` 方法返回 `List<User>`,任何人都可以访问你所有用户的密码,即使你没有在屏幕上显示它. 这不仅关乎安全,还关乎数据隐藏. 应用程序服务应该只返回表示层(或客户端)所需要的内容,不多也不少.
#### 序列化和延迟加载问题
当你将数据(一个对象)返回到表示层时,它很可能是序列化的. 例如在返回JSON的REST API中,你的对象将被序列化为JSON并发送给客户端. 在这方面将实体返回到表示层可能会有问题,尤其是在使用关系数据库和像Entity Framework Core这样的ORM提供者时.
在真实的应用程序中你的实体可以相互引用. `User` 实体可以引用它的角色. 如果你想序列化用户,它的角色也必须是序列化的. `Role` 类可以有 `List <Permission>`,而 `Permission` 类可以有一个对 `PermissionGroup` 类的引用,依此类推...想象一下所有这些对象都被立即序列化了. 你可能会意外地序列化整个数据库! 同样,如果你的对象具有循环引用,则它们可能根本**不会**序列化成功.
有什么解决方案? 将属性标记为 `NonSerialized` 吗? 不,你不知道什么时候应该序列化什么时候应该序列化. 一个应用程序服务方法可能需要它,而另一个则不需要. 在这种情况下返回安全,可序列化且经过特殊设计的DTO是一个不错的选择.
几乎所有的O/RM框架都支持延迟加载. 此功能可在需要时从数据库加载实体. 假设 `User` 类具有对 `Role` 类的引用. 当你从数据库中获取用户时,`Role` 属性(或集合)不会被立即填充. 首次读取 `Role` 属性时,它是从数据库加载的. 因此如果将这样的实体返回到表示层,它将通过执行额外的查询从数据库中检索额外的实体. 如果序列化工具读取实体,它会递归读取所有属性,并且可以再次检索整个数据库(如果实体之间存在关系).
如果在表示层中使用实体,可能会出现更多问题.**最好不要在表示层中引用领域/业务层程序集**.
如果你确定使用DTO,我们可以继续讨论ABP框架提供的关于dto的建议.
> ABP并不强迫你使用DTO,但是**强烈建议将DTO作为最佳实践**.
## 标准接口和基类
DTO是一个没有依赖性的简单类,你可以用任何方式进行设计. 但是ABP引入了一些**接口**来确定命名**标准属性**和**基类**的**约定**,以免在声明**公共属性**时**重复工作**.
**它们都不是必需的**,但是使用它们可以**简化和标准化**应用程序代码.
### 实体相关DTO
通常你需要创建与你的实体相对应的DTO,从而生成与实体类似的类. ABP框架在创建DTO时提供了一些基类来简化.
#### EntityDto
`IEntityDto<TKey>` 是一个只定义 `Id` 属性的简单接口. 你可以实现它或从 `EntityDto<TKey>` 继承.
**Example:**
````csharp
using System;
using Volo.Abp.Application.Dtos;
namespace AbpDemo
{
public class ProductDto : EntityDto<Guid>
{
public string Name { get; set; }
//...
}
}
````
#### 审计DTO
如果你的实体继承自被审计的实体类(或实现审计接口)可以使用以下基类来创建DTO:
* `CreationAuditedEntityDto`
* `CreationAuditedEntityWithUserDto`
* `AuditedEntityDto`
* `AuditedEntityWithUserDto`
* `FullAuditedEntityDto`
* `FullAuditedEntityWithUserDto`
#### 可扩展的DTO
如果你想为你的DTO使用[对象扩展系统](Object-Extensions.md),你可以使用或继承以下DTO类:
* `ExtensibleObject` 实现 `IHasExtraProperties` (其它类继承这个类).
* `ExtensibleEntityDto`
* `ExtensibleCreationAuditedEntityDto`
* `ExtensibleCreationAuditedEntityWithUserDto`
* `ExtensibleAuditedEntityDto`
* `ExtensibleAuditedEntityWithUserDto`
* `ExtensibleFullAuditedEntityDto`
* `ExtensibleFullAuditedEntityWithUserDto`
### 列表结果
通常将DTO列表返回给客户端. `IListResult<T>` 接口和 `ListResultDto<T>` 类用于使其成为标准.
`IListResult<T>` 接口的定义:
````csharp
public interface IListResult<T>
{
IReadOnlyList<T> Items { get; set; }
}
````
**示例: 返回产品列表**
````csharp
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
namespace AbpDemo
{
public class ProductAppService : ApplicationService, IProductAppService
{
private readonly IRepository<Product, Guid> _productRepository;
public ProductAppService(IRepository<Product, Guid> productRepository)
{
_productRepository = productRepository;
}
public async Task<ListResultDto<ProductDto>> GetListAsync()
{
//Get entities from the repository
List<Product> products = await _productRepository.GetListAsync();
//Map entities to DTOs
List<ProductDto> productDtos =
ObjectMapper.Map<List<Product>, List<ProductDto>>(products);
//Return the result
return new ListResultDto<ProductDto>(productDtos);
}
}
}
````
你可以简单地返回 `productDtos` 对象(并更改方法的返回类型), 这也没有错. 返回一个 `ListResultDto` 会使`List<ProductDto>` 做为 `Item` 属性包装到另一个对象中. 这具有一个优点:以后可以在不破坏远程客户端的情况下(当它们作为JSON结果获得值时)在返回值中添加更多属性. 在开发可重用的应用程序模块时特别建议使用这种方式.
### 分页 & 排序列表结果
从服务器请求分页列表并将分页列表返回给客户端是更常见的情况. ABP定义了一些接口和类来对其进行标准化:
#### 输入 (请求) 类型
以下接口和类用于标准化客户端发送的输入.
* `ILimitedResultRequest`: 定义 `MaxResultCount`(`int`) 属性从服务器请求指定数量的结果.
* `IPagedResultRequest`: 继承自 `ILimitedResultRequest` (所以它具有 `MaxResultCount` 属性)并且定义了 `SkipCount` (`int`)用于请求服务器的分页结果时跳过计数.
* `ISortedResultRequest`: 定义 `Sorting` (`string`)属性以请求服务器的排序结果. 排序值可以是“名称”,"*Name*", "*Name DESC*", "*Name ASC, Age DESC*"... 等.
* `IPagedAndSortedResultRequest` 继承自 `IPagedResultRequest``ISortedResultRequest`,所以它有上述所有属性.
建议你继承以下基类DTO类之一,而不是手动实现接口:
* `LimitedResultRequestDto` 实现了 `ILimitedResultRequest`.
* `PagedResultRequestDto` 实现了 `IPagedResultRequest` (和继承自 `LimitedResultRequestDto`).
* `PagedAndSortedResultRequestDto` 实现了 `IPagedAndSortedResultRequest` (和继承自 `PagedResultRequestDto`).
##### 最大返回数量
`LimitedResultRequestDto`(和其它固有的)通过以下规则限制和验证 `MaxResultCount`;
* 如果客户端未设置 `MaxResultCount`,则假定为**10**(默认页面大小). 可以通过设置 `LimitedResultRequestDto.DefaultMaxResultCoun` t静态属性来更改此值.
* 如果客户端发送的 `MaxResultCount` 大于*1,000**,则会产生**验证错误**. 保护服务器免受滥用服务很重要. 如果需要可以通过设置 `LimitedResultRequestDto.MaxMaxResultCount` 静态属性来更改此值.
建议在应用程序启动时设置静态属性,因为它们是静态的(全局).
#### 输出 (响应) 类型
以下接口和类用于标准化发送给客户端的输出.
* `IHasTotalCount` 定义 `TotalCount`(`long`)属性以在分页的情况下返回记录的总数.
* `IPagedResult<T>` 集成自 `IListResult<T>``IHasTotalCount`, 所以它有 `Items``TotalCount` 属性.
建议你继承以下基类DTO类之一,而不是手动实现接口:
* `PagedResultDto<T>` 继承自 `ListResultDto<T>` 和实现了 `IPagedResult<T>`.
**示例: 从服务器请求分页和排序的结果并返回分页列表**
````csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
namespace AbpDemo
{
public class ProductAppService : ApplicationService, IProductAppService
{
private readonly IRepository<Product, Guid> _productRepository;
public ProductAppService(IRepository<Product, Guid> productRepository)
{
_productRepository = productRepository;
}
public async Task<PagedResultDto<ProductDto>> GetListAsync(
PagedAndSortedResultRequestDto input)
{
//Create the query
var query = _productRepository
.OrderBy(input.Sorting)
.Skip(input.SkipCount)
.Take(input.MaxResultCount);
//Get total count from the repository
var totalCount = await query.CountAsync();
//Get entities from the repository
List<Product> products = await query.ToListAsync();
//Map entities to DTOs
List<ProductDto> productDtos =
ObjectMapper.Map<List<Product>, List<ProductDto>>(products);
//Return the result
return new PagedResultDto<ProductDto>(totalCount, productDtos);
}
}
}
````
ABP框架还定义了一种 `PageBy` 扩展方法(与`IPagedResultRequest`兼容),可用于代替 `Skip` + `Take`调用:
````csharp
var query = _productRepository
.OrderBy(input.Sorting)
.PageBy(input);
````
> 注意我们将`Volo.Abp.EntityFrameworkCore`包添加到项目中以使用 `ToListAsync``CountAsync` 方法,因为它们不包含在标准LINQ中,而是由Entity Framework Core定义.
如果你不了解示例代码,另请参阅[仓储文档](Repositories.md).
## 相关话题
### 验证
[应用服务](Application-Services.md)方法,控制器操作,页面模型输入...的输入会自动验证. 你可以使用标准数据注释属性或自定义验证方法来执行验证.
参阅[验证文档](Validation.md)了解更多.
### 对象到对象的映射
创建与实体相关的DTO时通常需要映射这些对象. ABP提供了一个对象到对象的映射系统简化映射过程. 请参阅以下文档:
* [对象到对象映射文档](Object-To-Object-Mapping.md)介绍了这些功能.
* [应用服务文档](Application-Services.md)提供了完整的示例.
## 最佳实践
你可以自由设计DTO类,然而这里有一些你可能想要遵循的最佳实践和建议.
### 共同原则
* DTO应该是**可序列化的**,因为它们通常是序列化和反序列化的(JSON或其他格式). 如果你有另一个带参数的构造函数,建议使用空(无参数)的公共构造函数.
* 除某些[验证](Validation.md)代码外,DTO**不应包含任何业务逻辑**.
* DTO不要继承实体,也**不要引用实体**. [应用程序启动模板](Startup-Templates/Application.md)已经通过分隔项目来阻止它.
* 如果你使用自动[对象到对象](Object-To-Object-Mapping.md)映射库,如AutoMapper,请启用**映射配置验证**以防止潜在的错误.
### 输入DTO原则
* 只定义用例**所需的属性**. 不要包含不用于用例的属性,这样做会使开发人员感到困惑.
* **不要在**不同的应用程序服务方法之间重用输入DTO. 因为不同的用例将需要和使用DTO的不同属性,从而导致某些属性在某些情况下没有使用,这使得理解和使用服务更加困难,并在将来导致潜在的错误.
### 输出DTO原则
* 如果在所有情况下填充**所有属性**,就可以**重用输出DTO**.

2
docs/zh-Hans/Domain-Driven-Design.md

@ -2,7 +2,7 @@
## 什么是DDD?
ABP框架提供了**基础设施**使基于**DDD**的开发更易实现. DDD在[维基百科中的定义](https://zh.wikipedia.org/wiki/%E5%9F%9F%E9%A9%B1%E5%8A%A8%E5%BC%80%E5%8F%91)如下:
ABP框架提供了**基础设施**使基于**领域驱动设计**的开发更易实现. DDD在[维基百科中的定义](https://zh.wikipedia.org/wiki/%E5%9F%9F%E9%A9%B1%E5%8A%A8%E5%BC%80%E5%8F%91)如下:
> **领域驱动设计(DDD)** 是一种通过将实现连接到持续进化的模型来满足复杂需求的软件开发方法. 领域驱动设计的前提是:
>

6
docs/zh-Hans/Entities.md

@ -26,9 +26,9 @@ public class Book : Entity<Guid>
如果你的实体Id类型为 `Guid`,有一些好的实践可以实现:
* 创建一个构造函数,获取ID作为参数传递给基类.
* 如果没有为GUID Id斌值,ABP框架会在保存时设置它,但是在将实体保存到数据库之前最好在实体上有一个有效的Id.
* 如果使用带参数的构造函数创建实体,那么还要创建一个 `protected` 构造函数. 当数据库提供程序从数据库读取你的实体时(反序列化时)将使用它.
* 不要使用 `Guid.NewGuid()` 来设置Id! 在创建实体的代码中使用[`IGuidGenerator`服务](Guid-Generation.md)传递Id参数. `IGuidGenerator`经过优化可以产生连续的GUID.这对于关系数据库中的聚集索引非常重要.
* 如果没有为GUID Id斌值,**ABP框架会在保存时设置它**,但是在将实体保存到数据库之前最好在实体上有一个有效的Id.
* 如果使用带参数的构造函数创建实体,那么还要创建一个 `private``protected` 构造函数. 当数据库提供程序从数据库读取你的实体时(反序列化时)将使用它.
* 不要使用 `Guid.NewGuid()` 来设置Id! 在创建实体的代码中**使用[`IGuidGenerator`服务](Guid-Generation.md)**传递Id参数. `IGuidGenerator`经过优化可以产生连续的GUID.这对于关系数据库中的聚集索引非常重要.
示例实体:

112
docs/zh-Hans/Guid-Generation.md

@ -1,3 +1,111 @@
## Guid 生成
# GUID 生成
待添加
GUID是数据库管理系统中使用的常见**主键类型**, ABP框架更喜欢GUID作为预构建[应用模块](Modules/Index.md)的主要对象. `ICurrentUser.Id` 属性([参见文档](CurrentUser.md))是GUID类型,这意味着ABP框架假定用户ID始终是GUID,
## 为什么偏爱GUID?
GUID有优缺点. 你可以在网上找到许多与此主题相关的文章,因此我们不再赘述,而是列出了最基本的优点:
* 它可在所有数据库提供程序中**使用**.
* 它允许在客户端**确定主键**,而不需要通过**数据库往返**来生成Id值. 在向数据库插入新记录时,这可以提高性能并允许我们在与数据库交互之前知道PK.
* GUID是**自然唯一的**在以下情况下有一些优势;
* 你需要与**外部**系统集成,
* 你需要**拆分或合并**不同的表.
* 你正在创建**分布式系统**
* GUID是无法猜测的,因此在某些情况下与自动递增的Id值相比,GUID**更安全**.
尽管存在一些缺点(只需在Web上搜索),但在设计ABP框架时我们发现这些优点更为重要.
## IGuidGenerator
GUID的最重要问题是**默认情况下它不是连续的**. 当你将GUID用作主键并将其设置为表的**聚集索引**(默认设置)时,这会在**插入时带来严重的性能问题**(因为插入新记录可能需要对现有记录进行重新排序).
所以,**永远不要为你的实体使用 `Guid.NewGuid()` 创建ID**!.
这个问题的一个好的解决方案是生成**连续的GUID**,由ABP框架提供的开箱即用的. `IGuidGenerator` 服务创建顺序的GUID(默认由 `SequentialGuidGenerator` 实现). 当需要手动设置[实体](Entities.md)的Id时,请使用 `IGuidGenerator.Create()`.
**示例: 具有GUID主键的实体并创建该实体**
假设你有一个具有 `Guid` 主键的 `Product` [实体](Entities.md):
````csharp
using System;
using Volo.Abp.Domain.Entities;
namespace AbpDemo
{
public class Product : AggregateRoot<Guid>
{
public string Name { get; set; }
private Product() { /* This constructor is used by the ORM/database provider */ }
public Product(Guid id, string name)
: base(id)
{
Name = name;
}
}
}
````
然后你想要创建一个产品:
````csharp
using System;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Guids;
namespace AbpDemo
{
public class MyProductService : ITransientDependency
{
private readonly IRepository<Product, Guid> _productRepository;
private readonly IGuidGenerator _guidGenerator;
public MyProductService(
IRepository<Product, Guid> productRepository,
IGuidGenerator guidGenerator)
{
_productRepository = productRepository;
_guidGenerator = guidGenerator;
}
public async Task CreateAsync(string productName)
{
var product = new Product(_guidGenerator.Create(), productName);
await _productRepository.InsertAsync(product);
}
}
}
````
该服务将 `IGuidGenerator` 注入构造函数中. 如果你的类是[应用服务](Application-Services.md)或派生自其他基类之一,可以直接使用 `GuidGenerator` 基类属性,该属性是预先注入的 `IGuidGenerator` 实例.
## Options
### AbpSequentialGuidGeneratorOptions
`AbpSequentialGuidGeneratorOptions` 是用于配置顺序生成GUID的[选项类](Options.md). 它只有一个属性:
* `DefaultSequentialGuidType` (`SequentialGuidType` 类型的枚举): 生成GUID值时使用的策略.
数据库提供程序在处理GUID时的行为有所不同,你应根据数据库提供程序进行设置. `SequentialGuidType` 有以下枚举成员:
* `SequentialAtEnd` (**default**) 用于[SQL Server](Entity-Framework-Core.md).
* `SequentialAsString` 用于[MySQL](Entity-Framework-Core-MySQL.md)和[PostgreSQL](Entity-Framework-Core-PostgreSQL.md).
* `SequentialAsBinary` 用于[Oracle](Entity-Framework-Core-Oracle.md).
在你的[模块](Module-Development-Basics.md)的 `ConfigureServices` 方法配置选项,如下:
````csharp
Configure<AbpSequentialGuidGeneratorOptions>(options =>
{
options.DefaultSequentialGuidType = SequentialGuidType.SequentialAsBinary;
});
````
> EF Core[集成包](https://docs.abp.io/en/abp/latest/Entity-Framework-Core-Other-DBMS)已为相关的DBMS设置相应的值. 如果你正在使用这些集成包,在大多数情况下则无需设置此选项.

4
docs/zh-Hans/Index.md

@ -10,8 +10,8 @@ ABP是一个**开源应用程序框架**,专注于基于ASP.NET Core的Web应用
使用ABP开发新项目的最简单方法是使用启动模板:
* [ASP.NET Core MVC (Razor Pages) UI 启动模板](Getting-Started?UI=MVC&DB=EF&Tiered=No)
* [Angular UI 启动模板](Getting-Started?UI=NG&DB=EF&Tiered=No)
* [ASP.NET Core MVC (Razor Pages) UI 启动模板](Getting-Started.md?UI=MVC&DB=EF&Tiered=No)
* [Angular UI 启动模板](Getting-Started.md?UI=NG&DB=EF&Tiered=No)
如果你想从头开始(使用空项目),请手动安装ABP框架并使用以下教程:

30
docs/zh-Hans/Modules/Docs.md

@ -58,14 +58,18 @@ ABP框架的[文档](docs.abp.io)也是使用的此模块.
输入用户名 `admin` 密码 `1q2w3E*` 登录到网站.
### 2- 引用文档模块包
### 3- 安装模块
文档模块包托管在Nuget上面. 需要有四个包安装到你的应用程序中. 每个包必须安装到相关的项目.
#### 3.1- 使用ABP CLI
建议使用ABP CLI安装模块,在解决方案文件 (`.sln`) 目录打开 `CMD` 窗口,运行以下命令:
`abp add-module Volo.Docs`
#### 3.2- 手动安装
或者你也可以手动安装nuget包到每个项目:
* 安装[Volo.Docs.Domain](https://www.nuget.org/packages/Volo.Docs.Domain/) nuget包到 `Acme.MyProject.Domain` 项目.
@ -83,7 +87,7 @@ ABP框架的[文档](docs.abp.io)也是使用的此模块.
* 安装[Volo.Docs.Web](https://www.nuget.org/packages/Volo.Docs.Domain/) nuget包到 `Acme.MyProject.Web` 项目.
`Install-Package Volo.Docs.Web`
### 3- 添加模块依赖
##### 3.2.1- 添加模块依赖
一个ABP模块必须声明 `[DependsOn]` attribute 如果它依赖于另一个模块. 每个模块都必须在相关的项目的`[DependsOn]`Attribute 中添加.
@ -122,7 +126,6 @@ ABP框架的[文档](docs.abp.io)也是使用的此模块.
}
```
* 打开 `MyProjectApplicationModule.cs`并且添加 `typeof(DocsApplicationModule)` 如下所示;
```csharp
@ -165,6 +168,27 @@ ABP框架的[文档](docs.abp.io)也是使用的此模块.
}
```
##### 3.2.2- 添加NPM包
打开 `package.json` 添加 `@abp/docs` 如下所示:
```json
{
"version": "1.0.0",
"name": "my-app",
"private": true,
"dependencies": {
"@abp/aspnetcore.mvc.ui.theme.basic": "^2.9.0",
"@abp/docs": "^2.9.0"
}
}
```
然后在 `Acme.MyProject.Web` 项目目录打开命令行终端运行以下命令:
1. `yarn`
2. `gulp`
### 4- 数据库集成
#### 4.1- Entity Framework 集成

8
docs/zh-Hans/Modules/Virtual-File-Explorer.md

@ -8,19 +8,21 @@
### 安装
#### 1- 引用虚拟文件浏览器模块包
#### 1- 使用ABP CLI
建议使用ABP CLI安装模块,在解决方案文件 (`.sln`) 目录打开 `CMD` 窗口,运行以下命令:
`abp add-module Volo.VirtualFileExplorer`
#### 2- 手动安装
或者你也可以手动安装nuget包到 `Acme.MyProject.Web` 项目:
* 安装[Volo.Abp.VirtualFileExplorer.Web](https://www.nuget.org/packages/Volo.Abp.VirtualFileExplorer.Web/) nuget包到 `Acme.MyProject.Web` 项目.
`Install-Package Volo.Abp.VirtualFileExplorer.Web`
#### 2- 添加模块依赖
##### 2.1- 添加模块依赖
* 打开 `MyProjectWebModule.cs` 并且添加 `typeof(AbpVirtualFileExplorerWebModule)` 如下所示;
@ -40,7 +42,7 @@
}
```
#### 3- 添加NPM包
##### 2.2- 添加NPM包
* 打开 `package.json` 添加 `@abp/virtual-file-explorer": "^2.9.0` 如下所示:

19
docs/zh-Hans/Startup-Templates/Console.md

@ -0,0 +1,19 @@
# 控制台应用程序启动模板
此模板用于创建一个最小的依赖关系的ABP控制台应用程序项目.
## 如何开始?
首先,如果你没有安装[ABP CLI](../CLI.md),请先安装它:
````bash
dotnet tool install -g Volo.Abp.Cli
````
在一个空文件夹使用 `abp new` 命令创建新解决方案:
````bash
abp new Acme.MyConsoleApp -t console
````
`Acme.MyConsoleApp` 是解决方案的名称, 如*YourCompany.YourProduct*. 你可以使用单级或多级名称.

3
docs/zh-Hans/Startup-Templates/Index.md

@ -5,4 +5,5 @@
单击下面列表中的名称以查看相关启动模板的文档:
* [**app**](Application.md): 应用程序模板.
* [**module**](Module.md): 模块/服务模板.
* [**module**](Module.md): 模块/服务模板.
* [**console**](Console.md): 控制台模板.

113
docs/zh-Hans/Timing.md

@ -0,0 +1,113 @@
# 时钟
使用时间和[时区](https://en.wikipedia.org/wiki/Time_zone)总是很棘手,尤其是当你需要构建供**不同时区**的用户使用的**全局系统**时.
ABP提供了一个基本的基础结构,使其变得容易并在可能的情况下自动进行处理. 本文档涵盖了与时间和时区相关的ABP框架服务和系统.
> 如果你正在创建在单个时区区域运行的本地应用程序,则可能不需要这些系统. 但也建议使用本文中介绍的 `IClock` 服务.
## IClock
`DateTime.Now` 返回带有**服务器本地日期和时间**的 `DateTime` 对象. `DateTime` 对象**不存储时区信息**. 因此你无法知道此对象中存储的**绝对日期和时间**. 你只能做一些**假设**,例如假设它是在UTC+05时区创建的. 当你此值保存到数据库中并稍后读取,或发送到**不同时区**的客户端时,事情就变得特别复杂.
解决此问题的一种方法是始终使用 `DateTime.UtcNow` 并将所有 `DateTime` 对象假定为UTC时间. 在这种情况下你可以在需要时将其转换为目标客户端的时区.
`IClock` 在获取当前时间的同时提供了一种抽象,你可以在应用程序中的单个点上控制日期时间的类型(UTC或本地时间).
**示例: 获取当前时间**
````csharp
using Volo.Abp.DependencyInjection;
using Volo.Abp.Timing;
namespace AbpDemo
{
public class MyService : ITransientDependency
{
private readonly IClock _clock;
public MyService(IClock clock)
{
_clock = clock;
}
public void Foo()
{
//Get the current time!
var now = _clock.Now;
}
}
}
````
* 当你需要获取当前时间时注入 `IClock` 服务. 常用的服务基类(如ApplicationService)已经注入并且做为基类属性提供,所以你可以直接使用 `Clock`.
* 使用 `Now` 属性获取当前时间.
> 在大多数情况下 `IClock` 是你需要在应用程序中了解和使用的唯一服务.
### Clock 选项
`AbpClockOptions` 是用于设置时钟种类的[选项](Options.md)类.
**示例: 使用 UTC Clock**
````csharp
Configure<AbpClockOptions>(options =>
{
options.Kind = DateTimeKind.Utc;
});
````
在你的[模块](Module-Development-Basics.md)的 `ConfigureServices` 方法添加以上内容.
> 默认 `Kind``Unspecified`,实际上使时钟不存在. 如果要利用Clock系统,要么使用 `Utc``Local`.
### DateTime 标准化
`IClock` 的其他重要功能是规范化 `DateTime` 对象.
**示例用法 :**
````csharp
DateTime dateTime = ...; //Get from somewhere
var normalizedDateTime = Clock.Normalize(dateTime)
````
`Normalize` 方法的工作原理如下:
* 如果当前时钟为UTC,并且给定的 `DateTime` 为本地时间,将给定的 `DateTime` 转换为UTC(通过使用 `DateTime.ToUniversalTime()` 方法).
* 如果当前时钟是本地的,并且给定的 `DateTime` 是UTC,将给定的 `DateTime` 转换为本地时间(通过使用 `DateTime.ToUniversalTime()` 方法).
* 如果未指定给定的 `DateTime``Kind`,将给定的 `DateTime``Kind`(使用 `DateTime.SpecifyKind(...)` 方法)设置为当前时钟的 `Kind`.
当获取的 `DateTime` 不是由 `IClock` 创建且可能与当前Clock类型不兼容的时候,ABP框架会使用 `Normalize` 方法. 例如;
* ASP.NET Core MVC模型绑定中的 `DateTime` 类型绑定.
* 通过[Entity Framework Core](Entity-Framework-Core.md)将数据保存到数据库或从数据库读取数据.
* 在[JSON反序列化](Json.md)上使用 `DateTime` 对象.
#### DisableDateTimeNormalization Attribute
`DisableDateTimeNormalization` attribute可用于禁用所需类或属性的规范化操作.
### 其他 IClock 属性
除了 `Now`, `IClock` 服务还具有以下属性:
* `Kind`: 返回当前使用的时钟类型(`DateTimeKind.Utc`, `DateTimeKind.Local``DateTimeKind.Unspecified`)的 `DateTimeKind`.
* `SupportsMultipleTimezone`: 如果当前时间是UTC,则返回 `true`.
## 时区
本节介绍与管理时区有关的ABP框架基础结构
### 时区设置
ABP框架定义了一个名为 `Abp.Timing.Timezone` 的**设置**,可用于为应用程序的用户,[租户](Multi-Tenancy.md)或全局设置和获取时区. 默认值为 `UTC`.
参阅[设置系统]了解更多关于设置系统.
### ITimezoneProvider
`ITimezoneProvider` 是一个服务,可将[Windows时区ID](https://support.microsoft.com/en-us/help/973627/microsoft-time-zone-index-values)值简单转换为[Iana时区名称](https://www.iana.org/time-zones)值,反之亦然. 它还提供了获取这些时区列表与获取具有给定名称的 `TimeZoneInfo` 的方法.
它已使用[TimeZoneConverter](https://github.com/mj1856/TimeZoneConverter)库实现.

4
docs/zh-Hans/UI/Angular/Component-Replacement.md

@ -543,6 +543,10 @@ export class AppComponent implements OnInit {
![New nav-items](./images/replaced-nav-items-component.png)
## 另请参阅
- [如何替换PermissionManagementComponent](./Permission-Management-Component-Replacement.md)
## 下一步是什么?
- [自定义设置页面](./Custom-Setting-Page.md)

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

@ -0,0 +1,500 @@
# 如何替换 PermissionManagementComponent
![权限管理模态框](./images/permission-management-modal.png)
`angular` 文件夹中运行以下命令来创建一个名为 `PermissionManagementComponent` 新组件.
```bash
yarn ng generate component permission-management --entryComponent --inlineStyle
# You don't need the --entryComponent option in Angular 9
```
打开 `src/app/permission-management` 文件夹下生成的 `permission-management.component.ts` 用以下内容替换它:
```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], []);
}
```
打开 `src/app/permission-management` 文件夹下生成的 `permission-management.component.html` 用以下内容替换它:
```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>
```
打开 `src/app` 文件夹下的 `app.component.ts` 修改为以下内容:
```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,
})
);
}
}
```
## 另请参阅
- [组件替换](./Component-Replacement.md)

BIN
docs/zh-Hans/UI/Angular/images/layout-components.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

BIN
docs/zh-Hans/UI/Angular/images/logo-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

BIN
docs/zh-Hans/UI/Angular/images/nav-items-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 KiB

BIN
docs/zh-Hans/UI/Angular/images/replaced-logo-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

BIN
docs/zh-Hans/UI/Angular/images/replaced-nav-items-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
docs/zh-Hans/UI/Angular/images/replaced-routes-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
docs/zh-Hans/UI/Angular/images/routes-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

23
docs/zh-Hans/UI/AspNetCore/JavaScript-API/Index.md

@ -0,0 +1,23 @@
# JavaScript API
ABP为ASP.NET Core MVC / Razor页面应用程序提供了一些执行客户端常见需求的JavaScrpt Api.
## APIs
* abp.ajax
* [abp.auth]
* abp.currentUser
* abp.dom
* abp.event
* abp.features
* abp.localization
* abp.log
* abp.ModalManager
* abp.notify
* abp.security
* abp.setting
* abp.ui
* abp.utils
* abp.ResourceLoader
* abp.WidgetManager
* Other APIs

38
docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Badges.md

@ -0,0 +1,38 @@
# 徽章
## 结合扫
`abp-badge``abp-badge-pill` 是abp徽章标签.
基本用法:
````csharp
<span abp-badge="Primary">Primary</span>
<a abp-badge="Info" href="#">Info</a>
<a abp-badge-pill="Danger" href="#">Danger</a>
````
## Demo
参阅[徽章demo页面](https://bootstrap-taghelpers.abp.io/Components/Badges)查看示例.
### Values
* 表示徽章的类型. 应为下列值之一:
* `_` (默认值)
* `Default` (默认值)
* `Primary`
* `Secondary`
* `Success`
* `Danger`
* `Warning`
* `Info`
* `Light`
* `Dark`
示例:
````csharp
<span abp-badge-pill="Danger">Danger</span>
````

124
docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Borders.md

@ -0,0 +1,124 @@
# 边框
## 介绍
`abp-border` 是边框样式的主要元素.
基本用法:
````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
参阅[边框demo页面](https://bootstrap-taghelpers.abp.io/Components/Borders)查看示例.
## 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/zh-Hans/UI/AspNetCore/Tag-Helpers/Breadcrumbs.md

@ -0,0 +1,25 @@
# 面包屑
## Introduction
`ABP-breadcrumb` 是面包屑项主容器.
基本用法:
````csharp
<abp-breadcrumb>
<abp-breadcrumb-item href="#" title="Home" />
<abp-breadcrumb-item href="#" title="Library"/>
<abp-breadcrumb-item title="Page"/>
</abp-breadcrumb>
````
## Demo
参阅[面包屑demo页面](https://bootstrap-taghelpers.abp.io/Components/Breadcrumbs)查看示例.
## abp-breadcrumb-item Attributes
- **title**: 设置面包屑项文本.
- **active**: 设置活动面包屑项. 如果没有其他项是活动的,默认最后一项为活动项.
- **href**: 表示 `abp-breadcrumb-item` 是否有链接. 值应该是字符串链接.

37
docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Button-groups.md

@ -0,0 +1,37 @@
# 按钮组
## 介绍
`abp-button-group` 是创建分组按钮的主要元素.
基本用法:
````csharp
<abp-button-group>
<abp-button button-type="Secondary">Left</abp-button>
<abp-button button-type="Secondary">Middle</abp-button>
<abp-button button-type="Secondary">Right</abp-button>
</abp-button-group>
````
## Demo
参阅[按钮组demo页面](https://bootstrap-taghelpers.abp.io/Components/Button-groups)查看示例.
## Attributes
### direction
按钮的方向. 应为以下值之一:
* `Horizontal` (默认值)
* `Vertical`
### size
组中按钮的大小. 应为以下值之一:
* `Default` (默认值)
* `Small`
* `Medium`
* `Large`

71
docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Carousel.md

@ -0,0 +1,71 @@
# 轮播
## 介绍
`abp-carousel` 是abp标签轮播元素
基本用法:
````csharp
<abp-carousel>
<abp-carousel-item src=""></abp-carousel-item>
<abp-carousel-item src=""></abp-carousel-item>
<abp-carousel-item src=""></abp-carousel-item>
</abp-carousel>
````
## Demo
参阅[轮播demo页面](https://bootstrap-taghelpers.abp.io/Components/Carousel)查看示例.
## Attributes
### id
轮播的ID. 如果未设置则会生成一个ID.
### controls
用于启用轮播上的控件(previous和next按钮). 应为以下值之一:
* `false`
* `true`
### indicators
启用轮播指标. 应为以下值之一:
* `false`
* `true`
### crossfade
用于启用淡入淡出动画而不是在轮播上滑动. 应为以下值之一:
* `false`
* `true`
## abp-carousel-item Attributes
### caption-title
设置轮播项的标题
### caption
设置轮播项的说明.
### src
链接值设置显示在轮播项上的图像的来源.
### active
设置活动轮播项. 应为以下值之一:
* `false`
* `true`
### alt
当无法显示图像时,该值设置轮播项目图像的替代文本.

114
docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Navs.md

@ -0,0 +1,114 @@
# 导航
## 介绍
`abp-nav` 是从bootstrap nav元素派生的基本标签助手.
基本用法:
````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
参阅[导航demo页面](https://bootstrap-taghelpers.abp.io/Components/Navs)查看示例.
## abp-nav Attributes
- **nav-style**: 指示包含项的位置和样式. 应为以下值之一:
* `Default` (默认值)
* `Vertical`
* `Pill`
* `PillVertical`
- **align:** 指示包含项的对齐方式:
* `Default` (默认值)
* `Start`
* `Center`
* `End`
### abp-nav-bar Attributes
- **nav-style**: 指示基本导航栏的颜色布局. 应为以下值之一:
* `Default` (默认值)
* `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:** 指示基本导航栏的大小. 应为以下值之一:
* `Default` (默认值)
* `Sm`
* `Md`
* `Lg`
* `Xl`
### abp-nav-item Attributes
**dropdown**: 将导航项设置为下拉菜单(如果提供的话). 可以是下列值之一:
* `false` (默认值)
* `true`
示例:
````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>
````

59
docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Tables.md

@ -0,0 +1,59 @@
# 表格
## 介绍
`ABP-table` 在ABP中用于表格的基本标签组件.
基本用法:
````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
参阅[表格demo页面](https://bootstrap-taghelpers.abp.io/Components/Tables)查看示例.
## abp-table Attributes
- **responsive**: 用于创建直至特定断点的响应表. 请参阅[特定断点](https://getbootstrap.com/docs/4.1/content/tables/#breakpoint-specific)获取更多信息.
- **responsive-sm**: 如果没有设置为false,则为小屏幕设备设置表响应性.
- **responsive-md**: 如果未设置为false,则为中等屏幕设备设置表响应性.
- **responsive-lg**: 如果未设置为false,则为大屏幕设备设置表响应性.
- **responsive-xl**: 如果未设置为false,则为超大屏幕设备设置表响应性.
- **dark-theme**: 如果设置为true,则将表格颜色主题设置为黑暗.
- **striped-rows**: 如果设置为true,则将斑马条纹添加到表行中.
- **hoverable-rows**: 如果设置为true,则将悬停状态添加到表行.
- **border-style**: 设置表格的边框样式. 应为以下值之一:
- `Default` (默认)
- `Bordered`
- `Borderless`

88
docs/zh-Hans/docs-nav.json

@ -182,23 +182,46 @@
},
{
"text": "文本模板",
"path": "Text-Templating.md"
},
{
"text": "JSON序列化"
},
{
"text": "邮件"
"text": "BLOB存储",
"items": [
{
"text": "BLOB存储系统",
"path": "Blob-Storing.md"
},
{
"text": "存储提供程序",
"items": [
{
"text": "文件系统提供程序",
"path": "Blob-Storing-File-System.md"
},
{
"text": "数据库系统提供程序",
"path": "Blob-Storing-Database.md"
},
{
"text": "Azure提供程序",
"path": "Blob-Storing-Azure.md"
},
{
"text": "创建自定义提供程序",
"path": "Blob-Storing-Custom-Provider.md"
}
]
}
]
},
{
"text": "GUIDs"
"text": "文本模板",
"path": "Text-Templating.md"
},
{
"text": "线程"
"text": "GUID 生成",
"path": "Guid-Generation.md"
},
{
"text": "定时"
"text": "时钟",
"path": "Timing.md"
}
]
},
@ -260,7 +283,8 @@
"path": "Application-Services.md"
},
{
"text": "数据传输对象(DTO)"
"text": "数据传输对象(DTO)",
"path": "Data-Transfer-Objects.md"
},
{
"text": "工作单元"
@ -279,6 +303,15 @@
{
"text": "动态C# API客户端",
"path": "API/Dynamic-CSharp-API-Clients.md"
},
{
"text": "ABP端点",
"items": [
{
"text": "应用程序配置",
"path": "API/Application-Configuration.md"
}
]
}
]
},
@ -298,7 +331,17 @@
},
{
"text": "Tag Helpers",
"path": "UI/AspNetCore/Tag-Helpers/Index.md"
"path": "UI/AspNetCore/Tag-Helpers/Index.md",
"items": [
{
"text": "Form元素",
"path": "UI/AspNetCore/Tag-Helpers/Form-elements.md"
},
{
"text": "动态表单",
"path": "UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md"
}
]
},
{
"text": "仪表板和小部件(Widget)系统",
@ -451,6 +494,10 @@
"path": "Dapper.md"
}
]
},
{
"text": "种子数据",
"path": "Data-Seeding.md"
}
]
},
@ -510,6 +557,10 @@
{
"text": "模块",
"path": "Startup-Templates/Module.md"
},
{
"text": "控制台",
"path": "Startup-Templates/Console.md"
}
]
},
@ -534,16 +585,21 @@
"text": "微服务架构",
"path": "Microservice-Architecture.md"
},
{
"text": "测试"
},
{
"text": "每日构建",
"path": "Nightly-Builds.md"
},
{
"text": "路线图",
"path": "Road-Map.md"
},
{
"text": "贡献指南",
"path": "Contribution/Index.md"
},
{
"text": "API文档",
"path": "{ApiDocumentationUrl}"
}
]
}

14
framework/Volo.Abp.sln

@ -301,6 +301,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.FileSy
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.EntityFrameworkCore.Oracle.Devart", "src\Volo.Abp.EntityFrameworkCore.Oracle.Devart\Volo.Abp.EntityFrameworkCore.Oracle.Devart.csproj", "{75E5C841-5F36-4C44-A532-57CB8E7FFE15}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Azure", "src\Volo.Abp.BlobStoring.Azure\Volo.Abp.BlobStoring.Azure.csproj", "{C44242F7-D55D-4867-AAF4-A786E404312E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Azure.Tests", "test\Volo.Abp.BlobStoring.Azure.Tests\Volo.Abp.BlobStoring.Azure.Tests.csproj", "{A80E9A0B-8932-4B5D-83FB-6751708FD484}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -895,6 +899,14 @@ Global
{75E5C841-5F36-4C44-A532-57CB8E7FFE15}.Debug|Any CPU.Build.0 = Debug|Any CPU
{75E5C841-5F36-4C44-A532-57CB8E7FFE15}.Release|Any CPU.ActiveCfg = Release|Any CPU
{75E5C841-5F36-4C44-A532-57CB8E7FFE15}.Release|Any CPU.Build.0 = Release|Any CPU
{C44242F7-D55D-4867-AAF4-A786E404312E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C44242F7-D55D-4867-AAF4-A786E404312E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C44242F7-D55D-4867-AAF4-A786E404312E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C44242F7-D55D-4867-AAF4-A786E404312E}.Release|Any CPU.Build.0 = Release|Any CPU
{A80E9A0B-8932-4B5D-83FB-6751708FD484}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A80E9A0B-8932-4B5D-83FB-6751708FD484}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A80E9A0B-8932-4B5D-83FB-6751708FD484}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A80E9A0B-8932-4B5D-83FB-6751708FD484}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1047,6 +1059,8 @@ Global
{02B1FBE2-850E-4612-ABC6-DD62BCF2DD6B} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{68443D4A-1608-4039-B995-7AF4CF82E9F8} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{75E5C841-5F36-4C44-A532-57CB8E7FFE15} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{C44242F7-D55D-4867-AAF4-A786E404312E} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{A80E9A0B-8932-4B5D-83FB-6751708FD484} = {447C8A77-E5F0-4538-8687-7383196D04EA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5}

4
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Carousel/AbpCarouselTagHelperService.cs

@ -38,6 +38,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Carousel
protected virtual void SetItems(TagHelperContext context, TagHelperOutput output, List<CarouselItem> itemList)
{
var itemsHtml = new StringBuilder("");
itemsHtml.Append("<div class= \"carousel-inner\">");
foreach (var carouselItem in itemList)
{
@ -46,6 +47,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Carousel
itemsHtml.AppendLine(carouselItem.Html);
}
itemsHtml.Append("</div>");
output.Content.SetHtmlContent(itemsHtml.ToString());
}
@ -137,4 +139,4 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Carousel
}
}
}
}

5
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperResourceService.cs

@ -73,7 +73,10 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling.TagHelpers
throw new AbpException($"Could not find the bundle file '{bundleFile}' from {nameof(IWebContentFileProvider)}");
}
AddHtmlTag(viewContext, context, output, bundleFile + "?_v=" + file.LastModified.UtcTicks);
if (file.Length > 0)
{
AddHtmlTag(viewContext, context, output, bundleFile + "?_v=" + file.LastModified.UtcTicks);
}
}
stopwatch.Stop();

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Resources/IWebRequestResources.cs

@ -4,6 +4,6 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Resources
{
public interface IWebRequestResources
{
List<string> TryAdd(IEnumerable<string> resources);
List<string> TryAdd(List<string> resources);
}
}

28
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Resources/WebRequestResources.cs

@ -1,23 +1,35 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Http;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.Mvc.UI.Resources
{
public class WebRequestResources : IWebRequestResources, IScopedDependency
{
protected List<string> Resources { get; }
protected Dictionary<string, List<string>> Resources { get; }
public WebRequestResources()
protected IHttpContextAccessor HttpContextAccessor{ get; }
public WebRequestResources(IHttpContextAccessor httpContextAccessor)
{
Resources = new List<string>();
HttpContextAccessor = httpContextAccessor;
Resources = new Dictionary<string, List<string>>();
}
public List<string> TryAdd(IEnumerable<string> resources)
public List<string> TryAdd(List<string> resources)
{
var resourceToBeAdded = resources.Except(Resources).ToList();
Resources.AddRange(resourceToBeAdded);
return resourceToBeAdded;
var path = HttpContextAccessor.HttpContext?.Request?.Path ?? "";
if (Resources.TryGetValue(path, out var res))
{
var resourceToBeAdded = resources.Except(res).ToList();
res.AddRange(resourceToBeAdded);
return resourceToBeAdded;
}
Resources.Add(path, resources);
return resources;
}
}
}
}

24
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs

@ -188,7 +188,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
ThreeLetterIsoLanguageName = CultureInfo.CurrentUICulture.ThreeLetterISOLanguageName,
DateTimeFormat = new DateTimeFormatDto
{
CalendarAlgorithmType = CultureInfo.CurrentUICulture.DateTimeFormat.Calendar.AlgorithmType.ToString(),
CalendarAlgorithmType =
CultureInfo.CurrentUICulture.DateTimeFormat.Calendar.AlgorithmType.ToString(),
DateTimeFormatLong = CultureInfo.CurrentUICulture.DateTimeFormat.LongDatePattern,
ShortDatePattern = CultureInfo.CurrentUICulture.DateTimeFormat.ShortDatePattern,
FullDateTimePattern = CultureInfo.CurrentUICulture.DateTimeFormat.FullDateTimePattern,
@ -238,25 +239,24 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
protected virtual async Task<TimingDto> GetTimingConfigAsync()
{
var result = new TimingDto();
var windowsTimeZoneId = await _settingProvider.GetOrNullAsync(TimingSettingNames.TimeZone);
if (!windowsTimeZoneId.IsNullOrWhiteSpace())
return new TimingDto
{
result.TimeZone = new TimeZone
TimeZone = new TimeZone
{
Windows = new WindowsTimeZone
{
TimeZoneId = windowsTimeZoneId
},
Iana = new IanaTimeZone()
Iana = new IanaTimeZone
{
TimeZoneName = _timezoneProvider.WindowsToIana(windowsTimeZoneId)
TimeZoneName = windowsTimeZoneId.IsNullOrWhiteSpace()
? null
: _timezoneProvider.WindowsToIana(windowsTimeZoneId)
}
};
}
return result;
}
};
}
protected virtual ClockDto GetClockConfig()
@ -267,4 +267,4 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
};
}
}
}
}

4
framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueue.cs

@ -181,7 +181,7 @@ namespace Volo.Abp.BackgroundJobs.RabbitMQ
var context = new JobExecutionContext(
scope.ServiceProvider,
JobConfiguration.JobType,
Serializer.Deserialize(ea.Body, typeof(TArgs))
Serializer.Deserialize(ea.Body.ToArray(), typeof(TArgs))
);
try
@ -210,4 +210,4 @@ namespace Volo.Abp.BackgroundJobs.RabbitMQ
}
}
}
}
}

3
framework/src/Volo.Abp.BlobStoring.Azure/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
framework/src/Volo.Abp.BlobStoring.Azure/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

22
framework/src/Volo.Abp.BlobStoring.Azure/Volo.Abp.BlobStoring.Azure.csproj

@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyName>Volo.Abp.BlobStoring.Azure</AssemblyName>
<PackageId>Volo.Abp.BlobStoring.Azure</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.BlobStoring\Volo.Abp.BlobStoring.csproj" />
<PackageReference Include="Azure.Storage.Blobs" Version="12.4.3" />
</ItemGroup>
</Project>

10
framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AbpBlobStoringAzureModule.cs

@ -0,0 +1,10 @@
using Volo.Abp.Modularity;
namespace Volo.Abp.BlobStoring.Azure
{
[DependsOn(typeof(AbpBlobStoringModule))]
public class AbpBlobStoringAzureModule : AbpModule
{
}
}

24
framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobContainerConfigurationExtensions.cs

@ -0,0 +1,24 @@
using System;
namespace Volo.Abp.BlobStoring.Azure
{
public static class AzureBlobContainerConfigurationExtensions
{
public static AzureBlobProviderConfiguration GetAzureConfiguration(
this BlobContainerConfiguration containerConfiguration)
{
return new AzureBlobProviderConfiguration(containerConfiguration);
}
public static BlobContainerConfiguration UseAzure(
this BlobContainerConfiguration containerConfiguration,
Action<AzureBlobProviderConfiguration> azureConfigureAction)
{
containerConfiguration.ProviderType = typeof(AzureBlobProvider);
azureConfigureAction(new AzureBlobProviderConfiguration(containerConfiguration));
return containerConfiguration;
}
}
}

110
framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProvider.cs

@ -0,0 +1,110 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Azure.Storage.Blobs;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.BlobStoring.Azure
{
public class AzureBlobProvider : BlobProviderBase, ITransientDependency
{
protected IAzureBlobNameCalculator AzureBlobNameCalculator { get; }
public AzureBlobProvider(IAzureBlobNameCalculator azureBlobNameCalculator)
{
AzureBlobNameCalculator = azureBlobNameCalculator;
}
public override async Task SaveAsync(BlobProviderSaveArgs args)
{
var blobName = AzureBlobNameCalculator.Calculate(args);
var configuration = args.Configuration.GetAzureConfiguration();
if (!args.OverrideExisting && await BlobExistsAsync(args, blobName))
{
throw new BlobAlreadyExistsException($"Saving BLOB '{args.BlobName}' does already exists in the container '{GetContainerName(args)}'! Set {nameof(args.OverrideExisting)} if it should be overwritten.");
}
if (configuration.CreateContainerIfNotExists)
{
await CreateContainerIfNotExists(args);
}
await GetBlobClient(args, blobName).UploadAsync(args.BlobStream, true);
}
public override async Task<bool> DeleteAsync(BlobProviderDeleteArgs args)
{
var blobName = AzureBlobNameCalculator.Calculate(args);
if (await BlobExistsAsync(args, blobName))
{
return await GetBlobClient(args, blobName).DeleteIfExistsAsync();
}
return false;
}
public override async Task<bool> ExistsAsync(BlobProviderExistsArgs args)
{
var blobName = AzureBlobNameCalculator.Calculate(args);
return await BlobExistsAsync(args, blobName);
}
public override async Task<Stream> GetOrNullAsync(BlobProviderGetArgs args)
{
var blobName = AzureBlobNameCalculator.Calculate(args);
if (!await BlobExistsAsync(args, blobName))
{
return null;
}
var blobClient = GetBlobClient(args, blobName);
var download = await blobClient.DownloadAsync();
var memoryStream = new MemoryStream();
await download.Value.Content.CopyToAsync(memoryStream);
return memoryStream;
}
protected virtual BlobClient GetBlobClient(BlobProviderArgs args, string blobName)
{
var blobContainerClient = GetBlobContainerClient(args);
return blobContainerClient.GetBlobClient(blobName);
}
protected virtual BlobContainerClient GetBlobContainerClient(BlobProviderArgs args)
{
var configuration = args.Configuration.GetAzureConfiguration();
var blobServiceClient = new BlobServiceClient(configuration.ConnectionString);
return blobServiceClient.GetBlobContainerClient(GetContainerName(args));
}
protected virtual async Task CreateContainerIfNotExists(BlobProviderArgs args)
{
var blobContainerClient = GetBlobContainerClient(args);
await blobContainerClient.CreateIfNotExistsAsync();
}
private async Task<bool> BlobExistsAsync(BlobProviderArgs args, string blobName)
{
// Make sure Blob Container exists.
return await ContainerExistsAsync(GetBlobContainerClient(args)) &&
(await GetBlobClient(args, blobName).ExistsAsync()).Value;
}
private static string GetContainerName(BlobProviderArgs args)
{
var configuration = args.Configuration.GetAzureConfiguration();
return configuration.ContainerName.IsNullOrWhiteSpace()
? args.ContainerName
: configuration.ContainerName;
}
private static async Task<bool> ContainerExistsAsync(BlobContainerClient blobContainerClient)
{
return (await blobContainerClient.ExistsAsync()).Value;
}
}
}

39
framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfiguration.cs

@ -0,0 +1,39 @@
namespace Volo.Abp.BlobStoring.Azure
{
public class AzureBlobProviderConfiguration
{
public string ConnectionString
{
get => _containerConfiguration.GetConfiguration<string>(AzureBlobProviderConfigurationNames.ConnectionString);
set => _containerConfiguration.SetConfiguration(AzureBlobProviderConfigurationNames.ConnectionString, Check.NotNullOrWhiteSpace(value, nameof(value)));
}
/// <summary>
/// This name may only contain lowercase letters, numbers, and hyphens, and must begin with a letter or a number.
/// Each hyphen must be preceded and followed by a non-hyphen character.
/// The name must also be between 3 and 63 characters long.
/// If this parameter is not specified, the ContainerName of the <see cref="BlobProviderArgs"/> will be used.
/// </summary>
public string ContainerName
{
get => _containerConfiguration.GetConfiguration<string>(AzureBlobProviderConfigurationNames.ContainerName);
set => _containerConfiguration.SetConfiguration(AzureBlobProviderConfigurationNames.ContainerName, Check.NotNullOrWhiteSpace(value, nameof(value)));
}
/// <summary>
/// Default value: false.
/// </summary>
public bool CreateContainerIfNotExists
{
get => _containerConfiguration.GetConfigurationOrDefault(AzureBlobProviderConfigurationNames.CreateContainerIfNotExists, false);
set => _containerConfiguration.SetConfiguration(AzureBlobProviderConfigurationNames.CreateContainerIfNotExists, value);
}
private readonly BlobContainerConfiguration _containerConfiguration;
public AzureBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration)
{
_containerConfiguration = containerConfiguration;
}
}
}

9
framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobProviderConfigurationNames.cs

@ -0,0 +1,9 @@
namespace Volo.Abp.BlobStoring.Azure
{
public static class AzureBlobProviderConfigurationNames
{
public const string ConnectionString = "Azure.ConnectionString";
public const string ContainerName = "Azure.ContainerName";
public const string CreateContainerIfNotExists = "Azure.CreateContainerIfNotExists";
}
}

22
framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/DefaultAzureBlobNameCalculator.cs

@ -0,0 +1,22 @@
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
namespace Volo.Abp.BlobStoring.Azure
{
public class DefaultAzureBlobNameCalculator : IAzureBlobNameCalculator, ITransientDependency
{
protected ICurrentTenant CurrentTenant { get; }
public DefaultAzureBlobNameCalculator(ICurrentTenant currentTenant)
{
CurrentTenant = currentTenant;
}
public virtual string Calculate(BlobProviderArgs args)
{
return CurrentTenant.Id == null
? $"host/{args.BlobName}"
: $"tenants/{CurrentTenant.Id.Value.ToString("D")}/{args.BlobName}";
}
}
}

7
framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/IAzureBlobNameCalculator.cs

@ -0,0 +1,7 @@
namespace Volo.Abp.BlobStoring.Azure
{
public interface IAzureBlobNameCalculator
{
string Calculate(BlobProviderArgs args);
}
}

2
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderArgs.cs

@ -28,4 +28,4 @@ namespace Volo.Abp.BlobStoring
CancellationToken = cancellationToken;
}
}
}
}

7
framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyCrudAppService.cs

@ -6,7 +6,6 @@ using Volo.Abp.Application.Dtos;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Linq;
using Volo.Abp.MultiTenancy;
using Volo.Abp.ObjectMapping;
@ -66,7 +65,6 @@ namespace Volo.Abp.Application.Services
ICrudAppService<TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
where TEntity : class, IEntity
{
public IAsyncQueryableExecuter AsyncQueryableExecuter { get; set; }
protected IRepository<TEntity> Repository { get; }
@ -83,7 +81,6 @@ namespace Volo.Abp.Application.Services
protected AbstractKeyCrudAppService(IRepository<TEntity> repository)
{
Repository = repository;
AsyncQueryableExecuter = DefaultAsyncQueryableExecuter.Instance;
}
public virtual async Task<TGetOutputDto> GetAsync(TKey id)
@ -100,12 +97,12 @@ namespace Volo.Abp.Application.Services
var query = CreateFilteredQuery(input);
var totalCount = await AsyncQueryableExecuter.CountAsync(query);
var totalCount = await AsyncExecuter.CountAsync(query);
query = ApplySorting(query, input);
query = ApplyPaging(query, input);
var entities = await AsyncQueryableExecuter.ToListAsync(query);
var entities = await AsyncExecuter.ToListAsync(query);
return new PagedResultDto<TGetListOutputDto>(
totalCount,

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

@ -9,9 +9,11 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Aspects;
using Volo.Abp.Auditing;
using Volo.Abp.Authorization;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Features;
using Volo.Abp.Guids;
using Volo.Abp.Linq;
using Volo.Abp.Localization;
using Volo.Abp.MultiTenancy;
using Volo.Abp.ObjectMapping;
@ -59,6 +61,9 @@ namespace Volo.Abp.Application.Services
protected IUnitOfWorkManager UnitOfWorkManager => LazyGetRequiredService(ref _unitOfWorkManager);
private IUnitOfWorkManager _unitOfWorkManager;
protected IAsyncQueryableExecuter AsyncExecuter => LazyGetRequiredService(ref _asyncExecuter);
private IAsyncQueryableExecuter _asyncExecuter;
protected Type ObjectMapperContext { get; set; }
protected IObjectMapper ObjectMapper

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

@ -3,6 +3,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.Guids;
using Volo.Abp.Linq;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Timing;
@ -38,6 +39,9 @@ namespace Volo.Abp.Domain.Services
protected ICurrentTenant CurrentTenant => LazyGetRequiredService(ref _currentTenant);
private ICurrentTenant _currentTenant;
protected IAsyncQueryableExecuter AsyncExecuter => LazyGetRequiredService(ref _asyncExecuter);
private IAsyncQueryableExecuter _asyncExecuter;
protected ILogger Logger => _lazyLogger.Value;
private Lazy<ILogger> _lazyLogger => new Lazy<ILogger>(() => LoggerFactory?.CreateLogger(GetType().FullName) ?? NullLogger.Instance, true);

14
framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/MySQL/AbpEntityFrameworkCoreMySQLModule.cs

@ -1,4 +1,5 @@
using Volo.Abp.Modularity;
using Volo.Abp.Guids;
using Volo.Abp.Modularity;
namespace Volo.Abp.EntityFrameworkCore.MySQL
{
@ -7,6 +8,15 @@ namespace Volo.Abp.EntityFrameworkCore.MySQL
)]
public class AbpEntityFrameworkCoreMySQLModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpSequentialGuidGeneratorOptions>(options =>
{
if (options.DefaultSequentialGuidType == null)
{
options.DefaultSequentialGuidType = SequentialGuidType.SequentialAsString;
}
});
}
}
}

14
framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/Oracle/Devart/AbpEntityFrameworkCoreOracleDevartModule.cs

@ -1,4 +1,5 @@
using Volo.Abp.Modularity;
using Volo.Abp.Guids;
using Volo.Abp.Modularity;
namespace Volo.Abp.EntityFrameworkCore.Oracle.Devart
{
@ -7,6 +8,15 @@ namespace Volo.Abp.EntityFrameworkCore.Oracle.Devart
)]
public class AbpEntityFrameworkCoreOracleDevartModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpSequentialGuidGeneratorOptions>(options =>
{
if (options.DefaultSequentialGuidType == null)
{
options.DefaultSequentialGuidType = SequentialGuidType.SequentialAsBinary;
}
});
}
}
}

14
framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/PostgreSql/AbpEntityFrameworkCorePostgreSqlModule.cs

@ -1,4 +1,5 @@
using Volo.Abp.Modularity;
using Volo.Abp.Guids;
using Volo.Abp.Modularity;
namespace Volo.Abp.EntityFrameworkCore.PostgreSql
{
@ -7,6 +8,15 @@ namespace Volo.Abp.EntityFrameworkCore.PostgreSql
)]
public class AbpEntityFrameworkCorePostgreSqlModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpSequentialGuidGeneratorOptions>(options =>
{
if (options.DefaultSequentialGuidType == null)
{
options.DefaultSequentialGuidType = SequentialGuidType.SequentialAsString;
}
});
}
}
}

14
framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo/Abp/EntityFrameworkCore/SqlServer/AbpEntityFrameworkCoreSqlServerModule.cs

@ -1,4 +1,5 @@
using Volo.Abp.Modularity;
using Volo.Abp.Guids;
using Volo.Abp.Modularity;
namespace Volo.Abp.EntityFrameworkCore.SqlServer
{
@ -7,6 +8,15 @@ namespace Volo.Abp.EntityFrameworkCore.SqlServer
)]
public class AbpEntityFrameworkCoreSqlServerModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpSequentialGuidGeneratorOptions>(options =>
{
if (options.DefaultSequentialGuidType == null)
{
options.DefaultSequentialGuidType = SequentialGuidType.SequentialAtEnd;
}
});
}
}
}

34
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EfCoreAsyncQueryableProvider.cs

@ -0,0 +1,34 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query.Internal;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Linq;
namespace Volo.Abp.EntityFrameworkCore
{
public class EfCoreAsyncQueryableProvider : IAsyncQueryableProvider, ITransientDependency
{
public bool CanExecute<T>(IQueryable<T> queryable)
{
return queryable.Provider is EntityQueryProvider;
}
public Task<int> CountAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default)
{
return queryable.CountAsync(cancellationToken);
}
public Task<List<T>> ToListAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default)
{
return queryable.ToListAsync(cancellationToken);
}
public Task<T> FirstOrDefaultAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default)
{
return queryable.FirstOrDefaultAsync(cancellationToken);
}
}
}

4
framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs

@ -83,7 +83,7 @@ namespace Volo.Abp.EventBus.RabbitMq
return;
}
var eventData = Serializer.Deserialize(ea.Body, eventType);
var eventData = Serializer.Deserialize(ea.Body.ToArray(), eventType);
await TriggerHandlersAsync(eventType, eventData);
}
@ -237,4 +237,4 @@ namespace Volo.Abp.EventBus.RabbitMq
return false;
}
}
}
}

18
framework/src/Volo.Abp.Guids/Volo/Abp/Guids/AbpSequentialGuidGeneratorOptions.cs

@ -3,13 +3,21 @@
public class AbpSequentialGuidGeneratorOptions
{
/// <summary>
/// Default value: <see cref="SequentialGuidType.SequentialAtEnd"/>.
/// Default value: null (unspecified).
/// Use <see cref="GetDefaultSequentialGuidType"/> method
/// to get the value on use, since it fall backs to a default value.
/// </summary>
public SequentialGuidType DefaultSequentialGuidType { get; set; }
public AbpSequentialGuidGeneratorOptions()
public SequentialGuidType? DefaultSequentialGuidType { get; set; }
/// <summary>
/// Get the <see cref="DefaultSequentialGuidType"/> value
/// or returns <see cref="SequentialGuidType.SequentialAtEnd"/>
/// if <see cref="DefaultSequentialGuidType"/> was null.
/// </summary>
public SequentialGuidType GetDefaultSequentialGuidType()
{
DefaultSequentialGuidType = SequentialGuidType.SequentialAtEnd;
return DefaultSequentialGuidType ??
SequentialGuidType.SequentialAtEnd;
}
}
}

2
framework/src/Volo.Abp.Guids/Volo/Abp/Guids/SequentialGuidGenerator.cs

@ -24,7 +24,7 @@ namespace Volo.Abp.Guids
public Guid Create()
{
return Create(Options.DefaultSequentialGuidType);
return Create(Options.GetDefaultSequentialGuidType());
}
public Guid Create(SequentialGuidType guidType)

2
framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs

@ -15,8 +15,6 @@ using Volo.Abp.EventBus.Local;
using Volo.Abp.Guids;
using Volo.Abp.MongoDB;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Reflection;
using Volo.Abp.Threading;
namespace Volo.Abp.Domain.Repositories.MongoDB
{

35
framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoDbAsyncQueryableProvider.cs

@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Linq;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
namespace Volo.Abp.MongoDB
{
public class MongoDbAsyncQueryableProvider : IAsyncQueryableProvider, ITransientDependency
{
public bool CanExecute<T>(IQueryable<T> queryable)
{
return queryable.Provider.GetType().Namespace?.StartsWith("MongoDB") ?? false;
}
public Task<int> CountAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default)
{
return ((IMongoQueryable<T>) queryable).CountAsync(cancellationToken);
}
public Task<List<T>> ToListAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default)
{
return ((IMongoQueryable<T>) queryable).ToListAsync(cancellationToken);
}
public Task<T> FirstOrDefaultAsync<T>(IQueryable<T> queryable, CancellationToken cancellationToken = default)
{
return ((IMongoQueryable<T>) queryable).FirstOrDefaultAsync(cancellationToken);
}
}
}

5
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs

@ -18,10 +18,6 @@ namespace Volo.Abp.ObjectExtending
[NotNull]
public Type Type { get; }
[NotNull]
[Obsolete("Add validation attributes to the Attributes list instead! ValidationAttributes property will be removed in future versions.")]
public List<ValidationAttribute> ValidationAttributes { get; }
[NotNull]
public List<Attribute> Attributes { get; }
@ -71,7 +67,6 @@ namespace Volo.Abp.ObjectExtending
Name = Check.NotNull(name, nameof(name));
Configuration = new Dictionary<object, object>();
ValidationAttributes = new List<ValidationAttribute>();
Attributes = new List<Attribute>();
Validators = new List<Action<ObjectExtensionPropertyValidationContext>>();

1
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoExtensions.cs

@ -10,7 +10,6 @@ namespace Volo.Abp.ObjectExtending
return propertyInfo
.Attributes
.OfType<ValidationAttribute>()
.Union(propertyInfo.ValidationAttributes)
.ToArray();
}
}

2
framework/src/Volo.Abp.RabbitMQ/Volo.Abp.RabbitMQ.csproj

@ -15,7 +15,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RabbitMQ.Client" Version="5.1.2" />
<PackageReference Include="RabbitMQ.Client" Version="6.1.0" />
<ProjectReference Include="..\Volo.Abp.Json\Volo.Abp.Json.csproj" />
<ProjectReference Include="..\Volo.Abp.Threading\Volo.Abp.Threading.csproj" />
</ItemGroup>

53
framework/src/Volo.Abp.Threading/Volo/Abp/Linq/AsyncQueryableExecuter.cs

@ -0,0 +1,53 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Linq
{
public class AsyncQueryableExecuter : IAsyncQueryableExecuter, ITransientDependency
{
protected IEnumerable<IAsyncQueryableProvider> Providers { get; }
public AsyncQueryableExecuter(IEnumerable<IAsyncQueryableProvider> providers)
{
Providers = providers;
}
public virtual Task<int> CountAsync<T>(
IQueryable<T> queryable,
CancellationToken cancellationToken = default)
{
var provider = FindProvider(queryable);
return provider != null
? provider.CountAsync(queryable, cancellationToken)
: Task.FromResult(queryable.Count());
}
public virtual Task<List<T>> ToListAsync<T>(
IQueryable<T> queryable,
CancellationToken cancellationToken = default)
{
var provider = FindProvider(queryable);
return provider != null
? provider.ToListAsync(queryable, cancellationToken)
: Task.FromResult(queryable.ToList());
}
public virtual Task<T> FirstOrDefaultAsync<T>(
IQueryable<T> queryable,
CancellationToken cancellationToken = default)
{
var provider = FindProvider(queryable);
return provider != null
? provider.FirstOrDefaultAsync(queryable, cancellationToken)
: Task.FromResult(queryable.FirstOrDefault());
}
protected virtual IAsyncQueryableProvider FindProvider<T>(IQueryable<T> queryable)
{
return Providers.FirstOrDefault(p => p.CanExecute(queryable));
}
}
}

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

Loading…
Cancel
Save