Browse Source

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

pull/3275/head
Oliver Cooper 7 years ago
parent
commit
797511812a
  1. 7
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
  2. 1
      common.DotSettings
  3. 5
      docs/en/Best-Practices/Entity-Framework-Core-Integration.md
  4. 34
      docs/en/Customizing-Application-Modules-Extending-Entities.md
  5. 2
      docs/en/Customizing-Application-Modules-Overriding-Services.md
  6. 11
      docs/en/Entities.md
  7. 105
      docs/en/Entity-Framework-Core-Migrations.md
  8. 112
      docs/en/Entity-Framework-Core.md
  9. 48
      docs/en/Exception-Handling.md
  10. 6
      docs/en/Getting-Started-With-Startup-Templates.md
  11. 2
      docs/en/Multi-Tenancy.md
  12. 3
      docs/en/Object-Extensions.md
  13. 15
      docs/en/Startup-Templates/Application.md
  14. 53
      docs/en/UI/Angular/Component-Replacement.md
  15. 4
      docs/en/UI/Angular/Config-State.md
  16. 4
      docs/en/UI/Angular/Custom-Setting-Page.md
  17. 209
      docs/en/UI/Angular/Http-Requests.md
  18. 6
      docs/en/UI/Angular/Localization.md
  19. 2
      docs/en/UI/Angular/Permission-Management.md
  20. 67
      docs/en/UI/Angular/Service-Proxies.md
  21. 113
      docs/en/UI/Angular/Track-By-Service.md
  22. BIN
      docs/en/UI/Angular/images/generated-files-via-generate-proxy.png
  23. 3
      docs/en/UI/AspNetCore/Libraries/DatatablesNet.md
  24. 92
      docs/en/UI/AspNetCore/Tag-Helpers/Collapse.md
  25. 97
      docs/en/UI/AspNetCore/Tag-Helpers/Dropdowns.md
  26. 10
      docs/en/UI/AspNetCore/Tag-Helpers/Index.md
  27. 78
      docs/en/UI/AspNetCore/Tag-Helpers/List-Groups.md
  28. 81
      docs/en/UI/AspNetCore/Tag-Helpers/Modals.md
  29. 57
      docs/en/UI/AspNetCore/Tag-Helpers/Paginator.md
  30. 70
      docs/en/UI/AspNetCore/Tag-Helpers/Progress-Bars.md
  31. 2
      docs/en/UI/AspNetCore/Tag-Helpers/Tabs.md
  32. 35
      docs/en/UI/AspNetCore/Tag-Helpers/Tooltips.md
  33. 1312
      docs/en/UI/Common/Utils/Linked-List.md
  34. 26
      docs/en/docs-nav.json
  35. 7
      docs/zh-Hans/Best-Practices/Entity-Framework-Core-Integration.md
  36. 24
      docs/zh-Hans/CLI.md
  37. 31
      docs/zh-Hans/Customizing-Application-Modules-Extending-Entities.md
  38. 167
      docs/zh-Hans/Customizing-Application-Modules-Overriding-Services.md
  39. 9
      docs/zh-Hans/Entities.md
  40. 100
      docs/zh-Hans/Entity-Framework-Core-Migrations.md
  41. 108
      docs/zh-Hans/Entity-Framework-Core.md
  42. 3
      docs/zh-Hans/Getting-Started-Angular-Template.md
  43. 6
      docs/zh-Hans/Getting-Started-With-Startup-Templates.md
  44. 10
      docs/zh-Hans/Startup-Templates/Application.md
  45. 468
      docs/zh-Hans/UI/AspNetCore/Customization-User-Interface.md
  46. 80
      docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Alerts.md
  47. 4
      docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Buttons.md
  48. 180
      docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Cards.md
  49. 90
      docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Collapse.md
  50. 93
      docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Dropdowns.md
  51. 273
      docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Grids.md
  52. 12
      docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Index.md
  53. 76
      docs/zh-Hans/UI/AspNetCore/Tag-Helpers/List-Groups.md
  54. 79
      docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Modals.md
  55. 55
      docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Paginator.md
  56. 68
      docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Progress-Bars.md
  57. 91
      docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Tabs.md
  58. 33
      docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Tooltips.md
  59. BIN
      docs/zh-Hans/UI/AspNetCore/Tag-Helpers/fa-address-card.png
  60. 5
      docs/zh-Hans/docs-nav.json
  61. 16
      framework/Volo.Abp.sln
  62. 11
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Controllers/ErrorController.cs
  63. 18
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionFilter.cs
  64. 8
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/AbpExceptionHandlingMiddleware.cs
  65. 37
      framework/src/Volo.Abp.AutoMapper/AutoMapper/AbpAutoMapperExtensibleDtoExtensions.cs
  66. 1
      framework/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj
  67. 5
      framework/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperModule.cs
  68. 6
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJobExecuter.cs
  69. 6
      framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueue.cs
  70. 19
      framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/AsyncPeriodicBackgroundWorkerBase.cs
  71. 18
      framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/PeriodicBackgroundWorkerBase.cs
  72. 44
      framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs
  73. 43
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GenerateProxyCommand.cs
  74. 28
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs
  75. 51
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs
  76. 40
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Analyticses/CliAnalyticsCollect.cs
  77. 76
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ConnectionStringChangeStep.cs
  78. 5
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateProjectBuildPipelineBuilder.cs
  79. 7
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuildArgs.cs
  80. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmGlobalPackagesChecker.cs
  81. 15
      framework/src/Volo.Abp.Core/Volo/Abp/Check.cs
  82. 4
      framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ExposedServiceExplorer.cs
  83. 32
      framework/src/Volo.Abp.Core/Volo/Abp/ExceptionHandling/ExceptionNotificationContext.cs
  84. 41
      framework/src/Volo.Abp.Core/Volo/Abp/ExceptionHandling/ExceptionNotifier.cs
  85. 27
      framework/src/Volo.Abp.Core/Volo/Abp/ExceptionHandling/ExceptionNotifierExtensions.cs
  86. 11
      framework/src/Volo.Abp.Core/Volo/Abp/ExceptionHandling/ExceptionSubscriber.cs
  87. 10
      framework/src/Volo.Abp.Core/Volo/Abp/ExceptionHandling/IExceptionNotifier.cs
  88. 10
      framework/src/Volo.Abp.Core/Volo/Abp/ExceptionHandling/IExceptionSubscriber.cs
  89. 61
      framework/src/Volo.Abp.Core/Volo/Abp/IO/FileHelper.cs
  90. 37
      framework/src/Volo.Abp.Core/Volo/Abp/Text/StringHelper.cs
  91. 1
      framework/src/Volo.Abp.Data/Volo.Abp.Data.csproj
  92. 4
      framework/src/Volo.Abp.Data/Volo/Abp/Data/AbpDataModule.cs
  93. 2
      framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/AuditedEntityWithUserDto.cs
  94. 4
      framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/CreationAuditedEntityWithUserDto.cs
  95. 35
      framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleAuditedEntityDto.cs
  96. 40
      framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleAuditedEntityWithUserDto.cs
  97. 35
      framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleCreationAuditedEntityDto.cs
  98. 32
      framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleCreationAuditedEntityWithUserDto.cs
  99. 28
      framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleEntityDto.cs
  100. 41
      framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleFullAuditedEntityDto.cs

7
abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json

@ -86,6 +86,11 @@
"AreYouSureYouWantToDeleteAllComputers": "Are you sure you want to delete all computers?", "AreYouSureYouWantToDeleteAllComputers": "Are you sure you want to delete all computers?",
"DeleteAll": "Delete all", "DeleteAll": "Delete all",
"DoYouWantToCreateNewUser": "Do you want to create new user?", "DoYouWantToCreateNewUser": "Do you want to create new user?",
"MasterModules": "Master Modules" "MasterModules": "Master Modules",
"OrganizationName": "Organization name",
"OrganizationNamePlaceholder": "Organization name...",
"UsernameOrEmail": "Username or email",
"UsernameOrEmailPlaceholder": "Username or email...",
"Member": "Member"
} }
} }

1
common.DotSettings

@ -20,4 +20,5 @@
<s:String x:Key="/Default/CodeStyle/Generate/=Overrides/Options/=Async/@EntryIndexedValue">False</s:String> <s:String x:Key="/Default/CodeStyle/Generate/=Overrides/Options/=Async/@EntryIndexedValue">False</s:String>
<s:String x:Key="/Default/CodeStyle/Generate/=Overrides/Options/=Mutable/@EntryIndexedValue">False</s:String> <s:String x:Key="/Default/CodeStyle/Generate/=Overrides/Options/=Mutable/@EntryIndexedValue">False</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SQL/@EntryIndexedValue">SQL</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SQL/@EntryIndexedValue">SQL</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Volo/@EntryIndexedValue">True</s:Boolean>
</wpf:ResourceDictionary> </wpf:ResourceDictionary>

5
docs/en/Best-Practices/Entity-Framework-Core-Integration.md

@ -89,13 +89,15 @@ public static class IdentityDbContextModelBuilderExtensions
builder.Entity<IdentityUser>(b => builder.Entity<IdentityUser>(b =>
{ {
b.ToTable(options.TablePrefix + "Users", options.Schema); b.ToTable(options.TablePrefix + "Users", options.Schema);
b.ConfigureByConvention();
//code omitted for brevity //code omitted for brevity
}); });
builder.Entity<IdentityUserClaim>(b => builder.Entity<IdentityUserClaim>(b =>
{ {
b.ToTable(options.TablePrefix + "UserClaims", options.Schema); b.ToTable(options.TablePrefix + "UserClaims", options.Schema);
b.ConfigureByConvention();
//code omitted for brevity //code omitted for brevity
}); });
@ -104,6 +106,7 @@ public static class IdentityDbContextModelBuilderExtensions
} }
```` ````
* **Do** call `b.ConfigureByConvention();` for each entity mapping (as shown above).
* **Do** create a **configuration options** class by inheriting from the `ModelBuilderConfigurationOptions`. Example: * **Do** create a **configuration options** class by inheriting from the `ModelBuilderConfigurationOptions`. Example:
````C# ````C#

34
docs/en/Customizing-Application-Modules-Extending-Entities.md

@ -4,7 +4,7 @@ In some cases, you may want to add some additional properties (and database fiel
## Extra Properties ## Extra Properties
[Extra properties](Entities.md) is a way of storing some additional data on an entity without changing it. The entity should implement the `IHasExtraProperties` interface to allow it. All the aggregate root entities defined in the pre-built modules implement the `IHasExtraProperties` interface, so you can store extra properties on these entities. [Extra properties](Entities.md) is a way of storing some additional data on an entity without changing it. The entity should implement the `IHasExtraProperties` interface to allow it. All the aggregate root entities defined in the pre-built modules implement the `IHasExtraProperties` interface, so you can store extra properties on these objects.
Example: Example:
@ -25,7 +25,36 @@ Extra properties are stored as a single `JSON` formatted string value in the dat
See the [entities document](Entities.md) for more about the extra properties system. See the [entities document](Entities.md) for more about the extra properties system.
> It is possible to perform a **business logic** based on the value of an extra property. You can **override** a service method and get or set the value as shown above. Overriding services will be discussed below. > It is possible to perform a **business logic** based on the value of an extra property. You can [override a service method](Customizing-Application-Modules-Overriding-Services.md), then get or set the value as shown above.
## Entity Extensions (EF Core)
As mentioned above, all extra properties of an entity are stored as a single JSON object in the database table. This is not so natural especially when you want to;
* Create **indexes** and **foreign keys** for an extra property.
* Write **SQL** or **LINQ** using the extra property (search table by the property value, for example).
* Creating your **own entity** maps to the same table, but defines an extra property as a **regular property** in the entity (see the [EF Core migration document](Entity-Framework-Core-Migrations.md) for more).
To overcome the difficulties described above, ABP Framework entity extension system for the Entity Framework Core that allows you to use the same extra properties API defined above, but store a desired property as a separate field in the database table.
Assume that you want to add a `SocialSecurityNumber` to the `IdentityUser` entity of the [Identity Module](Modules/Identity.md). You can use the `ObjectExtensionManager`:
````csharp
ObjectExtensionManager.Instance
.MapEfCoreProperty<IdentityUser, string>(
"SocialSecurityNumber",
b => { b.HasMaxLength(32); }
);
````
* You provide the `IdentityUser` as the entity name, `string` as the type of the new property, `SocialSecurityNumber` as the property name (also, the field name in the database table).
* You also need to provide an action that defines the database mapping properties using the [EF Core Fluent API](https://docs.microsoft.com/en-us/ef/core/modeling/entity-properties).
> This code part must be executed before the related `DbContext` used. The [application startup template](Startup-Templates/Application.md) defines a static class named `YourProjectNameEntityExtensions`. You can define your extensions in this class to ensure that it is executed in the proper time. Otherwise, you should handle it yourself.
Once you define an entity extension, you then need to use the standard [Add-Migration](https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/powershell#add-migration) and [Update-Database](https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/powershell#update-database) commands of the EF Core to create a code first migration class and update your database.
You can then use the same extra properties system defined in the previous section to manipulate the property over the entity.
## Creating a New Entity Maps to the Same Database Table/Collection ## Creating a New Entity Maps to the Same Database Table/Collection
@ -146,4 +175,5 @@ public class MyDistributedIdentityUserCreatedEventHandler :
## See Also ## See Also
* [Migration System for the EF Core](Entity-Framework-Core-Migrations.md)
* [Customizing the Existing Modules](Customizing-Application-Modules-Guide.md) * [Customizing the Existing Modules](Customizing-Application-Modules-Guide.md)

2
docs/en/Customizing-Application-Modules-Overriding-Services.md

@ -10,7 +10,7 @@ You have different options can be used based on your requirement those will be e
## Replacing an Interface ## Replacing an Interface
If given service defines an interface, like the `IdentityUserAppService` class implements the `IIdentityAppService`, you can re-implement the same interface and replace the current implementation by your class. Example: If given service defines an interface, like the `IdentityUserAppService` class implements the `IIdentityUserAppService`, you can re-implement the same interface and replace the current implementation by your class. Example:
````csharp ````csharp
public class MyIdentityUserAppService : IIdentityUserAppService, ITransientDependency public class MyIdentityUserAppService : IIdentityUserAppService, ITransientDependency

11
docs/en/Entities.md

@ -373,16 +373,19 @@ So, you can directly use the `ExtraProperties` property to use the dictionary A
The way to store this dictionary in the database depends on the database provider you're using. The way to store this dictionary in the database depends on the database provider you're using.
* For [Entity Framework Core](Entity-Framework-Core.md), it is stored in a single `ExtraProperties` field as a `JSON` string. Serializing to `JSON` and deserializing from the `JSON` are automatically done by the ABP Framework using the [value conversions](https://docs.microsoft.com/en-us/ef/core/modeling/value-conversions) system of the EF Core. * For [Entity Framework Core](Entity-Framework-Core.md), here are two type of configurations;
* By default, it is stored in a single `ExtraProperties` field as a `JSON` string (that means all extra properties stored in a single database table field). Serializing to `JSON` and deserializing from the `JSON` are automatically done by the ABP Framework using the [value conversions](https://docs.microsoft.com/en-us/ef/core/modeling/value-conversions) system of the EF Core.
* If you want, you can use the `ObjectExtensionManager` to define a separate table field for a desired extra property. Properties those are not configured through the `ObjectExtensionManager` will continue to use a single `JSON` field as described above. This feature is especially useful when you are using a pre-built [application module](Modules/Index.md) and want to [extend its entities](Customizing-Application-Modules-Extending-Entities.md). See the [EF Core integration document](Entity-Framework-Core.md) to learn how to use the `ObjectExtensionManager`.
* For [MongoDB](MongoDB.md), it is stored as a **regular field**, since MongoDB naturally supports this kind of [extra elements](https://mongodb.github.io/mongo-csharp-driver/1.11/serialization/#supporting-extra-elements) system. * For [MongoDB](MongoDB.md), it is stored as a **regular field**, since MongoDB naturally supports this kind of [extra elements](https://mongodb.github.io/mongo-csharp-driver/1.11/serialization/#supporting-extra-elements) system.
### Discussion for the Extra Properties ### Discussion for the Extra Properties
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: 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.
* It is **not fully type safe**. You normally **don't need** to 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. * It is **not easy to [auto map](Object-To-Object-Mapping.md)** these properties from/to other objects.
* It **doesn't create fields** in the database table for EF Core, so it will not be easy to create indexes or search/order by this field in the database side.
### Extra Properties Behind Entities ### Extra Properties Behind Entities

105
docs/en/Entity-Framework-Core-Migrations.md

@ -93,7 +93,7 @@ From the database point of view, there are three important projects those will b
This project has the `DbContext` class (`BookStoreDbContext` for this sample) of your application. This project has the `DbContext` class (`BookStoreDbContext` for this sample) of your application.
**Every module uses its own `DbContext` class** to access to the database. Likewise, your application has its own `DbContext`. You typically use this `DbContext` in your application code (in your custom [repositories](Repositories.md) if you follow the best practices). It is almost an empty `DbContext` since your application don't have any entities at the beginning, except the pre-defined `AppUser` entity: **Every module uses its own `DbContext` class** to access to the database. Likewise, your application has its own `DbContext`. You typically use this `DbContext` in your application code (in your [repositories](Repositories.md) if you follow the best practices). It is almost an empty `DbContext` since your application don't have any entities at the beginning, except the pre-defined `AppUser` entity:
````csharp ````csharp
[ConnectionStringName("Default")] [ConnectionStringName("Default")]
@ -117,15 +117,15 @@ public class BookStoreDbContext : AbpDbContext<BookStoreDbContext>
builder.Entity<AppUser>(b => builder.Entity<AppUser>(b =>
{ {
//Sharing the same table "AbpUsers" with the IdentityUser //Sharing the same Users table with the IdentityUser
b.ToTable("AbpUsers"); b.ToTable(AbpIdentityDbProperties.DbTablePrefix + "Users");
//Configure base properties
b.ConfigureByConvention(); b.ConfigureByConvention();
b.ConfigureAbpUser(); b.ConfigureAbpUser();
//Moved customization of the "AbpUsers" table to an extension method /* Configure mappings for your additional properties
b.ConfigureCustomUserProperties(); * Also see the MyProjectNameEntityExtensions class
*/
}); });
/* Configure your own tables/entities inside the ConfigureBookStore method */ /* Configure your own tables/entities inside the ConfigureBookStore method */
@ -188,12 +188,6 @@ public class BookStoreMigrationsDbContext : AbpDbContext<BookStoreMigrationsDbCo
builder.ConfigureFeatureManagement(); builder.ConfigureFeatureManagement();
builder.ConfigureTenantManagement(); builder.ConfigureTenantManagement();
/* Configure customizations for entities from the modules included */
builder.Entity<IdentityUser>(b =>
{
b.ConfigureCustomUserProperties();
});
/* Configure your own tables/entities inside the ConfigureBookStore method */ /* Configure your own tables/entities inside the ConfigureBookStore method */
builder.ConfigureBookStore(); builder.ConfigureBookStore();
} }
@ -274,7 +268,7 @@ In this way, the mapping configuration of a module can be shared between `DbCont
You may want to **reuse a table** of a depended module in your application. In this case, you have two options: You may want to **reuse a table** of a depended module in your application. In this case, you have two options:
1. You can **directly use the entity** defined by the module. 1. You can **directly use the entity** defined by the module (you can still [extend the entity](Customizing-Application-Modules-Extending-Entities.md) in some level).
2. You can **create a new entity** mapping to the same database table. 2. You can **create a new entity** mapping to the same database table.
###### Use the Entity Defined by a Module ###### Use the Entity Defined by a Module
@ -376,10 +370,8 @@ protected override void OnModelCreating(ModelBuilder builder)
builder.Entity<AppRole>(b => builder.Entity<AppRole>(b =>
{ {
b.ToTable("AbpRoles"); b.ToTable("AbpRoles");
b.ConfigureByConvention(); b.ConfigureByConvention();
b.Property(x => x.Title).HasMaxLength(128);
b.ConfigureCustomRoleProperties();
}); });
... ...
@ -395,69 +387,45 @@ We added the following lines:
````csharp ````csharp
builder.Entity<AppRole>(b => builder.Entity<AppRole>(b =>
{ {
b.ToTable("AbpRoles"); b.ToTable("AbpRoles");
b.ConfigureByConvention(); b.ConfigureByConvention();
b.Property(x => x.Title).HasMaxLength(128);
b.ConfigureCustomRoleProperties();
}); });
```` ````
* It maps to the same `AbpRoles` table shared with the `IdentityRole` entity. * It maps to the same `AbpRoles` table shared with the `IdentityRole` entity.
* `ConfigureByConvention()` configures the standard/base properties (like `TenantId`) and recommended to always call it. * `ConfigureByConvention()` configures the standard/base properties (like `TenantId`) and recommended to always call it.
`ConfigureCustomRoleProperties()` has not exists yet. Define it inside the `BookStoreDbContextModelCreatingExtensions` class (near to your `DbContext` in the `.EntityFrameworkCore` project): You've configured the custom property for your `DbContext` that is used by your application on the runtime. We also need to configure the `MigrationsDbContext`.
````csharp
public static void ConfigureCustomRoleProperties<TRole>(this EntityTypeBuilder<TRole> b)
where TRole : class, IEntity<Guid>
{
b.Property<string>(nameof(AppRole.Title)).HasMaxLength(128);
}
````
* This method only defines the **custom properties** of your entity.
* Unfortunately, we can not utilize the fully **type safety** here (by referencing the `AppRole` entity). The best we can do is to use the `Title` name as type safe. This is because of EF Core migration system can not map two unrelated entity classes to the same database table.
You've configured the custom property for your `DbContext` used by your application on the runtime. We also need to configure the `MigrationsDbContext`. Instead of directly changing the `MigrationsDbContext`, we should use the entity extension system of the ABP Framework. Find the `YourProjectNameEntityExtensions` class in the `.EntityFrameworkCore` project of your solution (`BookStoreEntityExtensions` for this example) and change it as shown below:
Open the `MigrationsDbContext` (`BookStoreMigrationsDbContext` for this example) and change as shown below:
````csharp ````csharp
protected override void OnModelCreating(ModelBuilder builder) public static class MyProjectNameEntityExtensions
{ {
base.OnModelCreating(builder); private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();
/* Include modules to your migration db context */ public static void Configure()
...
/* Configure customizations for entities from the modules included */
//CONFIGURE THE CUSTOM ROLE PROPERTIES
builder.Entity<IdentityRole>(b =>
{ {
b.ConfigureCustomRoleProperties(); OneTimeRunner.Run(() =>
}); {
ObjectExtensionManager.Instance
... .MapEfCoreProperty<IdentityRole, string>(
"Title",
/* Configure your own tables/entities inside the ConfigureBookStore method */ builder => { builder.HasMaxLength(64); }
);
builder.ConfigureBookStore(); });
}
} }
```` ````
Only added the following lines: > Instead of hard-coded "Title" string, we suggest to use `nameof(AppRole.Title)` or use a constant string.
````csharp `ObjectExtensionManager` is used to add properties to existing entities. Since `ObjectExtensionManager.Instance` is a static instance (singleton), we should call it once. `OneTimeRunner` is a simple utility class defined by the ABP Framework.
builder.Entity<IdentityRole>(b =>
{ See the [EF Core integration documentation](Entity-Framework-Core.md) for more about the entity extension system.
b.ConfigureCustomRoleProperties();
});
````
In this way, we re-used the extension method that is used to configure custom property mappings for the role. But, this time, did the same customization for the `IdentityRole` entity. > We've repeated a similar database mapping code, like `HasMaxLength(128)`, in both classes.
Now, you can add a new EF Core database migration using the standard `Add-Migration` command in the Package Manager Console (remember to select `.EntityFrameworkCore.DbMigrations` as the Default Project in the PMC and make sure that the `.Web` project is still the startup project): Now, you can add a new EF Core database migration using the standard `Add-Migration` command in the Package Manager Console (remember to select `.EntityFrameworkCore.DbMigrations` as the Default Project in the PMC and make sure that the `.Web` project is still the startup project):
@ -536,7 +504,7 @@ Instead of creating a new entity class to add a custom property, you can use the
###### Using the ExtraProperties ###### Using the ExtraProperties
All entities derived from the `AggregateRoot ` class can store name-value pairs in their `ExtraProperties` property, which is a `Dictionary<string, object>` serialized to JSON in the database table. So, you can add values to this dictionary and query again without changing the entity. All entities derived from the `AggregateRoot ` class can store name-value pairs in their `ExtraProperties` property (because they implement the `IHasExtraProperties` interface), which is a `Dictionary<string, object>` serialized to JSON in the database table. So, you can add values to this dictionary and query again without changing the entity.
For example, you can store query the title Property inside an `IdentityRole` instead of creating a new entity. Example: For example, you can store query the title Property inside an `IdentityRole` instead of creating a new entity. Example:
@ -553,16 +521,13 @@ public class IdentityRoleExtendingService : ITransientDependency
public async Task<string> GetTitleAsync(Guid id) public async Task<string> GetTitleAsync(Guid id)
{ {
var role = await _identityRoleRepository.GetAsync(id); var role = await _identityRoleRepository.GetAsync(id);
return role.GetProperty<string>("Title"); return role.GetProperty<string>("Title");
} }
public async Task SetTitleAsync(Guid id, string newTitle) public async Task SetTitleAsync(Guid id, string newTitle)
{ {
var role = await _identityRoleRepository.GetAsync(id); var role = await _identityRoleRepository.GetAsync(id);
role.SetProperty("Title", newTitle); role.SetProperty("Title", newTitle);
await _identityRoleRepository.UpdateAsync(role); await _identityRoleRepository.UpdateAsync(role);
} }
} }
@ -575,12 +540,20 @@ In this way, you can easily attach any type of value to an entity of a depended
* All the extra properties are stored as **a single JSON object** in the database. They are not stored as new table fields, as you may expect. Creating database table indexes and using SQL queries against these properties will be harder compared to simple table fields. * All the extra properties are stored as **a single JSON object** in the database. They are not stored as new table fields, as you may expect. Creating database table indexes and using SQL queries against these properties will be harder compared to simple table fields.
* Property names are strings, so they are **not type safe**. It is recommended to define constants for these kind of properties to prevent typo errors. * Property names are strings, so they are **not type safe**. It is recommended to define constants for these kind of properties to prevent typo errors.
###### Using the Entity Extensions System
Entity extension system solves the main problem of the extra properties: It can store an extra property in a **standard table field** in the database.
All you need to do is to use the `ObjectExtensionManager` to define the extra property as explained above, in the `AppRole` example. Then you can continue to use the same `GetProperty` and `SetProperty` methods defined above to get/set the related property on the entity, but this time stored as a separate field in the database.
###### Creating a New Table ###### Creating a New Table
Instead of creating a new entity and mapping to the same table, you can also create **your own table** to store your properties. You typically duplicate some values of the original entity. For example, you can add `Name` field to your own table which is a duplication of the `Name` field in the original table. Instead of creating a new entity and mapping to the same table, you can also create **your own table** to store your properties. You typically duplicate some values of the original entity. For example, you can add `Name` field to your own table which is a duplication of the `Name` field in the original table.
In this case, you don't deal with migration problems, however you need to deal with the problems of data duplication. When the duplicated value changes, you should reflect the same change in your table. You can use local or distributed [event bus](Event-Bus.md) to subscribe to the change events for the original entity. This is the recommended way of depending on a microservice's data from another microservice, especially if they have separate physical databases (you can search on the web on data sharing on a microservice design, it is a wide topic to cover here). In this case, you don't deal with migration problems, however you need to deal with the problems of data duplication. When the duplicated value changes, you should reflect the same change in your table. You can use local or distributed [event bus](Event-Bus.md) to subscribe to the change events for the original entity. This is the recommended way of depending on a microservice's data from another microservice, especially if they have separate physical databases (you can search on the web on data sharing on a microservice design, it is a wide topic to cover here).
> See the "[extending entities](Customizing-Application-Modules-Extending-Entities.md)" guide for more details on extending entities, including data duplication and synchronization tips.
#### Discussion of an Alternative Scenario: Every Module Manages Its Own Migration Path #### Discussion of an Alternative Scenario: Every Module Manages Its Own Migration Path
As mentioned before, `.EntityFrameworkCore.DbMigrations` merges all the database mappings of all the modules (plus your application's mappings) to create a unified migration path. As mentioned before, `.EntityFrameworkCore.DbMigrations` merges all the database mappings of all the modules (plus your application's mappings) to create a unified migration path.

112
docs/en/Entity-Framework-Core.md

@ -58,6 +58,53 @@ namespace MyCompany.MyProject
} }
```` ````
### About the EF Core Fluent Mapping
The [application startup template](Startup-Templates/Application.md) has been configured to use the [EF Core fluent configuration API](https://docs.microsoft.com/en-us/ef/core/modeling/) to map your entities to your database tables.
You can still use the **data annotation attributes** (like `[Required]`) on the properties of your entity while the ABP documentation generally follows the **fluent mapping API** approach. It is up to you.
ABP Framework has some **base entity classes** and **conventions** (see the [entities document](Entities.md)) and it provides some useful **extension methods** to configure the properties inherited from the base entity classes.
#### ConfigureByConvention Method
`ConfigureByConvention()` is the main extension method that **configures all the base properties** and conventions for your entities. So, it is a **best practice** to call this method for all your entities, in your fluent mapping code.
**Example**: Assume that you've a `Book` entity derived from `AggregateRoot<Guid>` base class:
````csharp
public class Book : AuditedAggregateRoot<Guid>
{
public string Name { get; set; }
}
````
You can override the `OnModelCreating` method in your `DbContext` and configure the mapping as shown below:
````csharp
protected override void OnModelCreating(ModelBuilder builder)
{
//Always call the base method
base.OnModelCreating(builder);
builder.Entity<Book>(b =>
{
b.ToTable("Books");
//Configure the base properties
b.ConfigureByConvention();
//Configure other properties (if you are using the fluent API)
b.Property(x => x.Name).IsRequired().HasMaxLength(128);
});
}
````
* Calling `b.ConfigureByConvention()` is important here to properly **configure the base properties**.
* You can configure the `Name` property here or you can use the **data annotation attributes** (see the [EF Core document](https://docs.microsoft.com/en-us/ef/core/modeling/entity-properties)).
> While there are many extension methods to configure your base properties, `ConfigureByConvention()` internally calls them if necessary. So, it is enough to call it.
### Configure the Connection String Selection ### Configure the Connection String Selection
If you have multiple databases in your application, you can configure the connection string name for your DbContext using the `[ConnectionStringName]` attribute. Example: If you have multiple databases in your application, you can configure the connection string name for your DbContext using the `[ConnectionStringName]` attribute. Example:
@ -225,7 +272,7 @@ public override async Task DeleteAsync(
} }
```` ````
### Access to the EF Core API ## Access to the EF Core API
In most cases, you want to hide EF Core APIs behind a repository (this is the main purpose of the repository pattern). However, if you want to access the `DbContext` instance over the repository, you can use `GetDbContext()` or `GetDbSet()` extension methods. Example: In most cases, you want to hide EF Core APIs behind a repository (this is the main purpose of the repository pattern). However, if you want to access the `DbContext` instance over the repository, you can use `GetDbContext()` or `GetDbSet()` extension methods. Example:
@ -251,9 +298,60 @@ public class BookService
> Important: You must reference to the `Volo.Abp.EntityFrameworkCore` package from the project you want to access to the DbContext. This breaks encapsulation, but this is what you want in that case. > Important: You must reference to the `Volo.Abp.EntityFrameworkCore` package from the project you want to access to the DbContext. This breaks encapsulation, but this is what you want in that case.
### Advanced Topics ## Extra Properties & Object Extension Manager
Extra Properties system allows you to set/get dynamic properties to entities those implement the `IHasExtraProperties` interface. It is especially useful when you want to add custom properties to the entities defined in an [application module](Modules/Index.md), when you use the module as package reference.
By default, all the extra properties of an entity are stored as a single `JSON` object in the database.
#### Set Default Repository Classes Entity extension system allows you to to store desired extra properties in separate fields in the related database table. For more information about the extra properties & the entity extension system, see the following documents:
* [Customizing the Application Modules: Extending Entities](Customizing-Application-Modules-Extending-Entities.md)
* [Entities](Entities.md)
This section only explains the EF Core related usage of the `ObjectExtensionManager`.
### ObjectExtensionManager.Instance
`ObjectExtensionManager` implements the singleton pattern, so you need to use the static `ObjectExtensionManager.Instance` to perform all the operations.
### MapEfCoreProperty
`MapEfCoreProperty` is a shortcut extension method to define an extension property for an entity and map to the database.
**Example**: Add `Title` property (database field) to the `IdentityRole` entity:
````csharp
ObjectExtensionManager.Instance
.MapEfCoreProperty<IdentityRole, string>(
"Title",
builder => { builder.HasMaxLength(64); }
);
````
If the related module has implemented this feature (by using the `ConfigureEfCoreEntity` explained below), then the new property is added to the model. Then you need to run the standard `Add-Migration` and `Update-Database` commands to update your database to add the new field.
>`MapEfCoreProperty` method must be called before using the related `DbContext`. It is a static method. The best way is to use it in your application as earlier as possible. The application startup template has a `YourProjectNameEntityExtensions` class that is safe to use this method inside.
### ConfigureEfCoreEntity
If you are building a reusable module and want to allow application developers to add properties to your entities, you can use the `ConfigureEfCoreEntity` extension method in your entity mapping. However, there is a shortcut extension method `ConfigureObjectExtensions` that can be used while configuring the entity mapping:
````csharp
builder.Entity<YourEntity>(b =>
{
b.ConfigureObjectExtensions();
//...
});
````
> If you call `ConfigureByConvention()` extension method (like `b.ConfigureByConvention()` for this example), ABP Framework internally calls the `ConfigureObjectExtensions` method. It is a **best practice** to use the `ConfigureByConvention()` method since it also configures database mapping for base properties by convention.
See the "*ConfigureByConvention Method*" section above for more information.
## Advanced Topics
### Set Default Repository Classes
Default generic repositories are implemented by `EfCoreRepository` class by default. You can create your own implementation and use it for all the default repository implementations. Default generic repositories are implemented by `EfCoreRepository` class by default. You can create your own implementation and use it for all the default repository implementations.
@ -299,7 +397,7 @@ context.Services.AddAbpDbContext<BookStoreDbContext>(options =>
}); });
``` ```
#### Set Base DbContext Class or Interface for Default Repositories ### Set Base DbContext Class or Interface for Default Repositories
If your DbContext inherits from another DbContext or implements an interface, you can use that base class or interface as DbContext for default repositories. Example: If your DbContext inherits from another DbContext or implements an interface, you can use that base class or interface as DbContext for default repositories. Example:
@ -331,7 +429,7 @@ public class BookRepository : EfCoreRepository<IBookStoreDbContext, Book, Guid>,
One advantage of using an interface for a DbContext is then it will be replaceable by another implementation. One advantage of using an interface for a DbContext is then it will be replaceable by another implementation.
#### Replace Other DbContextes ### Replace Other DbContextes
Once you properly define and use an interface for DbContext, then any other implementation can replace it using the `ReplaceDbContext` option: Once you properly define and use an interface for DbContext, then any other implementation can replace it using the `ReplaceDbContext` option:
@ -344,3 +442,7 @@ context.Services.AddAbpDbContext<OtherDbContext>(options =>
```` ````
In this example, `OtherDbContext` implements `IBookStoreDbContext`. This feature allows you to have multiple DbContext (one per module) on development, but single DbContext (implements all interfaces of all DbContexts) on runtime. In this example, `OtherDbContext` implements `IBookStoreDbContext`. This feature allows you to have multiple DbContext (one per module) on development, but single DbContext (implements all interfaces of all DbContexts) on runtime.
## See Also
* [Entities](Entities.md)

48
docs/en/Exception-Handling.md

@ -1,4 +1,4 @@
## Exception Handling # Exception Handling
ABP provides a built-in infrastructure and offers a standard model for handling exceptions in a web application. ABP provides a built-in infrastructure and offers a standard model for handling exceptions in a web application.
@ -7,7 +7,7 @@ ABP provides a built-in infrastructure and offers a standard model for handling
* Provides a configurable way to **localize** exception messages. * Provides a configurable way to **localize** exception messages.
* Automatically maps standard exceptions to **HTTP status codes** and provides a configurable option to map these to custom exceptions. * Automatically maps standard exceptions to **HTTP status codes** and provides a configurable option to map these to custom exceptions.
### Automatic Exception Handling ## Automatic Exception Handling
`AbpExceptionFilter` handles an exception if **any of the following conditions** are met: `AbpExceptionFilter` handles an exception if **any of the following conditions** are met:
@ -17,7 +17,7 @@ ABP provides a built-in infrastructure and offers a standard model for handling
If the exception is handled it's automatically **logged** and a formatted **JSON message** is returned to the client. If the exception is handled it's automatically **logged** and a formatted **JSON message** is returned to the client.
#### Error Message Format ### Error Message Format
Error Message is an instance of the `RemoteServiceErrorResponse` class. The simplest error JSON has a **message** property as shown below: Error Message is an instance of the `RemoteServiceErrorResponse` class. The simplest error JSON has a **message** property as shown below:
@ -83,11 +83,11 @@ Error **details** in an optional field of the JSON error message. Thrown `Except
`AbpValidationException` implements the `IHasValidationErrors` interface and it is automatically thrown by the framework when a request input is not valid. So, usually you don't need to deal with validation errors unless you have higly customised validation logic. `AbpValidationException` implements the `IHasValidationErrors` interface and it is automatically thrown by the framework when a request input is not valid. So, usually you don't need to deal with validation errors unless you have higly customised validation logic.
#### Logging ### Logging
Caught exceptions are automatically logged. Caught exceptions are automatically logged.
##### Log Level #### Log Level
Exceptions are logged with the `Error` level by default. The Log level can be determined by the exception if it implements the `IHasLogLevel` interface. Example: Exceptions are logged with the `Error` level by default. The Log level can be determined by the exception if it implements the `IHasLogLevel` interface. Example:
@ -100,7 +100,7 @@ public class MyException : Exception, IHasLogLevel
} }
```` ````
##### Self Logging Exceptions #### Self Logging Exceptions
Some exception types may need to write additional logs. They can implement the `IExceptionWithSelfLogging` if needed. Example: Some exception types may need to write additional logs. They can implement the `IExceptionWithSelfLogging` if needed. Example:
@ -116,7 +116,7 @@ public class MyException : Exception, IExceptionWithSelfLogging
> `ILogger.LogException` extension methods is used to write exception logs. You can use the same extension method when needed. > `ILogger.LogException` extension methods is used to write exception logs. You can use the same extension method when needed.
### Business Exceptions ## Business Exceptions
Most of your own exceptions will be business exceptions. The `IBusinessException` interface is used to mark an exception as a business exception. Most of your own exceptions will be business exceptions. The `IBusinessException` interface is used to mark an exception as a business exception.
@ -145,11 +145,11 @@ Volo.Qa:010002
* You can **directly throw** a `BusinessException` or **derive** your own exception types from it when needed. * You can **directly throw** a `BusinessException` or **derive** your own exception types from it when needed.
* All properties are optional for the `BusinessException` class. But you generally set either `ErrorCode` or `Message` property. * All properties are optional for the `BusinessException` class. But you generally set either `ErrorCode` or `Message` property.
### Exception Localization ## Exception Localization
One problem with throwing exceptions is how to localize error messages while sending it to the client. ABP offers two models and their variants. One problem with throwing exceptions is how to localize error messages while sending it to the client. ABP offers two models and their variants.
#### User Friendly Exception ### User Friendly Exception
If an exception implements the `IUserFriendlyException` interface, then ABP does not change it's `Message` and `Details` properties and directly send it to the client. If an exception implements the `IUserFriendlyException` interface, then ABP does not change it's `Message` and `Details` properties and directly send it to the client.
@ -192,7 +192,7 @@ Then the localization text can be:
* The `IUserFriendlyException` interface is derived from the `IBusinessException` and the `UserFriendlyException` class is derived from the `BusinessException` class. * The `IUserFriendlyException` interface is derived from the `IBusinessException` and the `UserFriendlyException` class is derived from the `BusinessException` class.
#### Using Error Codes ### Using Error Codes
`UserFriendlyException` is fine, but it has a few problems in advanced usages: `UserFriendlyException` is fine, but it has a few problems in advanced usages:
@ -230,7 +230,7 @@ throw new BusinessException(QaDomainErrorCodes.CanNotVoteYourOwnAnswer);
* Throwing any exception implementing the `IHasErrorCode` interface behaves the same. So, the error code localization approach is not unique to the `BusinessException` class. * Throwing any exception implementing the `IHasErrorCode` interface behaves the same. So, the error code localization approach is not unique to the `BusinessException` class.
* Defining localized string is not required for an error message. If it's not defined, ABP sends the default error message to the client. It does not use the `Message` property of the exception! if you want that, use the `UserFriendlyException` (or use an exception type that implements the `IUserFriendlyException` interface). * Defining localized string is not required for an error message. If it's not defined, ABP sends the default error message to the client. It does not use the `Message` property of the exception! if you want that, use the `UserFriendlyException` (or use an exception type that implements the `IUserFriendlyException` interface).
##### Using Message Parameters #### Using Message Parameters
If you have a parameterized error message, then you can set it with the exception's `Data` property. For example: If you have a parameterized error message, then you can set it with the exception's `Data` property. For example:
@ -265,7 +265,7 @@ Then the localized text can contain the `UserName` parameter:
* `WithData` can be chained with more than one parameter (like `.WithData(...).WithData(...)`). * `WithData` can be chained with more than one parameter (like `.WithData(...).WithData(...)`).
### HTTP Status Code Mapping ## HTTP Status Code Mapping
ABP tries to automatically determine the most suitable HTTP status code for common exception types by following these rules: ABP tries to automatically determine the most suitable HTTP status code for common exception types by following these rules:
@ -280,7 +280,7 @@ ABP tries to automatically determine the most suitable HTTP status code for comm
The `IHttpExceptionStatusCodeFinder` is used to automatically determine the HTTP status code. The default implementation is the `DefaultHttpExceptionStatusCodeFinder` class. It can be replaced or extended as needed. The `IHttpExceptionStatusCodeFinder` is used to automatically determine the HTTP status code. The default implementation is the `DefaultHttpExceptionStatusCodeFinder` class. It can be replaced or extended as needed.
#### Custom Mappings ### Custom Mappings
Automatic HTTP status code determination can be overrided by custom mappings. For example: Automatic HTTP status code determination can be overrided by custom mappings. For example:
@ -291,7 +291,27 @@ services.Configure<AbpExceptionHttpStatusCodeOptions>(options =>
}); });
```` ````
### Built-In Exceptions ## Subscribing to the Exceptions
It is possible to be informed when the ABP Framework **handles an exception**. It automatically **logs** all the exceptions to the standard [logger](Logging.md), but you may want to do more.
In this case, create a class derived from the `ExceptionSubscriber` class in your application:
````csharp
public class MyExceptionSubscriber : ExceptionSubscriber
{
public override async Task HandleAsync(ExceptionNotificationContext context)
{
//TODO...
}
}
````
The `context` object contains necessary information about the exception occurred.
> You can have multiple subscribers, each gets a copy of the exception. Exceptions thrown by your subscriber is ignored (but still logged).
## Built-In Exceptions
Some exception types are automatically thrown by the framework: Some exception types are automatically thrown by the framework:

6
docs/en/Getting-Started-With-Startup-Templates.md

@ -0,0 +1,6 @@
# Getting Started with the Startup Templates
See the following tutorials to learn how to get started with the ABP Framework using the pre-built application startup templates:
* [Getting Started With the ASP.NET Core MVC / Razor Pages UI](Getting-Started-AspNetCore-MVC-Template.md)
* [Getting Started with the Angular UI](Getting-Started-Angular-Template.md)

2
docs/en/Multi-Tenancy.md

@ -4,7 +4,7 @@ ABP Multi-tenancy module provides base functionality to create multi tenant appl
Wikipedia [defines](https://en.wikipedia.org/wiki/Multitenancy) multi-tenancy as like that: Wikipedia [defines](https://en.wikipedia.org/wiki/Multitenancy) multi-tenancy as like that:
> Software **Multi-tenancy** refers to a software **architecture** in which a **single instance** of a software runs on a server and serves **multiple tenants**. A tenant is a group of users who share a common access with specific privileges to the software instance. With a multitenant architecture, a software application is designed to provide every tenant a **dedicated share of the instance including its data**, configuration, user management, tenant individual functionality and non-functional properties. Multi-tenancy contrasts with multi-instance architectures, where separate software instances operate on behalf of different tenants. > Software **Multi-tenancy** refers to a software **architecture** in which a **single instance** of software runs on a server and serves **multiple tenants**. A tenant is a group of users who share a common access with specific privileges to the software instance. With a multitenant architecture, a software application is designed to provide every tenant a **dedicated share of the instance including its data**, configuration, user management, tenant individual functionality and non-functional properties. Multi-tenancy contrasts with multi-instance architectures, where separate software instances operate on behalf of different tenants.
### Volo.Abp.MultiTenancy Package ### Volo.Abp.MultiTenancy Package

3
docs/en/Object-Extensions.md

@ -0,0 +1,3 @@
# Object Extensions
TODO

15
docs/en/Startup-Templates/Application.md

@ -2,11 +2,12 @@
## Introduction ## Introduction
This template provides a layered application structure based on the [Domain Driven Design](../Domain-Driven-Design.md) (DDD) practices. This document explains the solution structure and projects in details. If you want to start quickly, follow the guides below: This template provides a layered application structure based on the [Domain Driven Design](../Domain-Driven-Design.md) (DDD) practices.
* See [Getting Started With the ASP.NET Core MVC Template](../Getting-Started-AspNetCore-MVC-Template.md) to create a new solution and run it for this template (uses MVC as the UI framework and Entity Framework Core as the database provider). This document explains **the solution structure** and projects in details. If you want to start quickly, follow the guides below:
* See the [ASP.NET Core MVC Application Development Tutorial](../Tutorials/Part-1.md?UI=MVC) to learn how to develop applications using this template (uses MVC as the UI framework and Entity Framework Core as the database provider).
* See the [Angular Application Development Tutorial](../Tutorials/Part-1.md?UI=NG) to learn how to develop applications using this template (uses Angular as the UI framework and MongoDB as the database provider). * [The getting started document](../Getting-Started-With-Startup-Templates.md) explains how to create a new application in a few minutes.
* [The application development tutorial](../Tutorials/Part-1) explains step by step application development.
## How to Start With? ## How to Start With?
@ -123,6 +124,8 @@ Notice that the migration `DbContext` is only used for database migrations and *
* Depends on the `.EntityFrameworkCore` project since it re-uses the configuration defined for the `DbContext` of the application. * Depends on the `.EntityFrameworkCore` project since it re-uses the configuration defined for the `DbContext` of the application.
> This project is available only if you are using EF Core as the database provider. > This project is available only if you are using EF Core as the database provider.
>
> See the [Entity Framework Core Migrations Guide](../Entity-Framework-Core-Migrations.md) to understand this project in details.
#### .DbMigrator Project #### .DbMigrator Project
@ -269,5 +272,5 @@ The files under the `angular/src/environments` folder has the essential configur
## What's Next? ## What's Next?
- See [Getting Started With the ASP.NET Core MVC Template](../Getting-Started-AspNetCore-MVC-Template.md) to create a new solution and run it for this template. - [The getting started document](../Getting-Started-With-Startup-Templates.md) explains how to create a new application in a few minutes.
- See the [ASP.NET Core MVC Tutorial](../Tutorials/Part-1.md) to learn how to develop applications using this template. - [The application development tutorial](../Tutorials/Part-1) explains step by step application development.

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

@ -1,10 +1,10 @@
# Component Replacement ## Component Replacement
You can replace some ABP components with your custom components. You can replace some ABP components with your custom components.
The reason that you **can replace** but **cannot customize** default ABP components is disabling or changing a part of that component can cause problems. So we named those components as _Replaceable Components_. The reason that you **can replace** but **cannot customize** default ABP components is disabling or changing a part of that component can cause problems. So we named those components as _Replaceable Components_.
## How to Replace a Component ### How to Replace a Component
Create a new component that you want to use instead of an ABP component. Add that component to `declarations` and `entryComponents` in the `AppModule`. Create a new component that you want to use instead of an ABP component. Add that component to `declarations` and `entryComponents` in the `AppModule`.
@ -29,7 +29,54 @@ export class AppComponent {
![Example Usage](./images/component-replacement.gif) ![Example Usage](./images/component-replacement.gif)
## Available Replaceable Components
### How to Replace a Layout
Each ABP theme module has 3 layouts named `ApplicationLayoutComponent`, `AccountLayoutComponent`, `EmptyLayoutComponent`. These layouts can be replaced with the same way.
> A layout component template should contain `<router-outlet></router-outlet>` element.
The below example describes how to replace the `ApplicationLayoutComponent`:
Run the following command to generate a layout in `angular` folder:
```bash
yarn ng generate component shared/my-application-layout --export --entryComponent
# You don't need the --entryComponent option in Angular 9
```
Add the following code in your layout template (`my-layout.component.html`) where you want the page to be loaded.
```html
<router-outlet></router-outlet>
```
Open the `app.component.ts` and add the below content:
```js
import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
import { MyApplicationLayoutComponent } from './shared/my-application-layout/my-application-layout.component'; // imported MyApplicationLayoutComponent
import { Store } from '@ngxs/store'; // imported Store
//...
export class AppComponent {
constructor(..., private store: Store) {} // injected Store
ngOnInit() {
// added below content
this.store.dispatch(
new AddReplaceableComponent({
component: MyApplicationLayoutComponent,
key: 'Theme.ApplicationLayoutComponent',
}),
);
//...
}
}
```
### Available Replaceable Components
| Component key | Description | | Component key | Description |
| -------------------------------------------------- | --------------------------------------------- | | -------------------------------------------------- | --------------------------------------------- |

4
docs/en/UI/Angular/Config-State.md

@ -288,3 +288,7 @@ Note that **you do not have to call this method at application initiation**, bec
#### Environment Properties #### Environment Properties
Please refer to `Config.Environment` type for all the properties you can pass to `dispatchSetEnvironment` as parameter. It can be found in the [config.ts file](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/config.ts#L13). Please refer to `Config.Environment` type for all the properties you can pass to `dispatchSetEnvironment` as parameter. It can be found in the [config.ts file](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/config.ts#L13).
## What's Next?
* [Component Replacement](./Component-Replacement.md)

4
docs/en/UI/Angular/Custom-Setting-Page.md

@ -40,3 +40,7 @@ ngOnInit() {
Navigate to `/setting-management` route to see the changes: Navigate to `/setting-management` route to see the changes:
![Custom Settings Tab](./images/custom-settings.png) ![Custom Settings Tab](./images/custom-settings.png)
## What's Next?
- [TrackByService](./Track-By-Service.md)

209
docs/en/UI/Angular/Http-Requests.md

@ -0,0 +1,209 @@
# How to Make HTTP Requests
## About HttpClient
Angular has the amazing [HttpClient](https://angular.io/guide/http) for communication with backend services. It is a layer on top and a simplified representation of [XMLHttpRequest Web API](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest). It also is the recommended agent by Angular for any HTTP request. There is nothing wrong with using the `HttpClient` in your ABP project.
However, `HttpClient` leaves error handling to the caller (method). In other words, HTTP errors are handled manually and by hooking into the observer of the `Observable` returned.
```js
getConfig() {
this.http.get(this.configUrl).subscribe(
config => this.updateConfig(config),
error => {
// Handle error here
},
);
}
```
Although clear and flexible, handling errors this way is repetitive work, even when error processing is delegated to the store or any other injectable.
An `HttpInterceptor` is able to catch `HttpErrorResponse`  and can be used for a centralized error handling. Nevertheless, cases where default error handler, therefore the interceptor, must be disabled require additional work and comprehension of Angular internals. Check [this issue](https://github.com/angular/angular/issues/20203) for details.
## RestService
ABP core module has a utility service for HTTP requests: `RestService`. Unless explicitly configured otherwise, it catches HTTP errors and dispatches a `RestOccurError` action. This action is then captured by the `ErrorHandler` introduced by the `ThemeSharedModule`. Since you should already import this module in your app, when the `RestService` is used, all HTTP errors get automatically handled by deafult.
### Getting Started with RestService
In order to use the `RestService`, you must inject it in your class as a dependency.
```js
import { RestService } from '@abp/ng.core';
@Injectable({
/* class metadata here */
})
class DemoService {
constructor(private rest: RestService) {}
}
```
You do not have to provide the `RestService` at module or component/directive level, because it is already **provided in root**.
### How to Make a Request with RestService
You can use the `request` method of the `RestService` is for HTTP requests. Here is an example:
```js
getFoo(id: number) {
const request: Rest.Request<null> = {
method: 'GET',
url: '/api/some/path/to/foo/' + id,
};
return this.rest.request<null, FooResponse>(request);
}
```
The `request` method always returns an `Observable<T>`. Therefore you can do the following wherever you use `getFoo` method:
```js
doSomethingWithFoo(id: number) {
this.demoService.getFoo(id).subscribe(
foo => {
// Do something with foo.
}
)
}
```
**You do not have to worry about unsubscription.** The `RestService` uses `HttpClient` behind the scenes, so every observable it returns is a finite observable, i.e. it closes subscriptions automatically upon success or error.
As you see, `request` method gets a request options object with `Rest.Request<T>` type. This generic type expects the interface of the request body. You may pass `null` when there is no body, like in a `GET` or a `DELETE` request. Here is an example where there is one:
```js
postFoo(body: Foo) {
const request: Rest.Request<Foo> = {
method: 'POST',
url: '/api/some/path/to/foo',
body
};
return this.rest.request<Foo, FooResponse>(request);
}
```
You may [check here](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/rest.ts#L23) for complete `Rest.Request<T>` type, which has only a few chages compared to [HttpRequest](https://angular.io/api/common/http/HttpRequest) class in Angular.
### How to Disable Default Error Handler of RestService
The `request` method, used with defaults, always handles errors. Let's see how you can change that behavior and handle errors yourself:
```js
deleteFoo(id: number) {
const request: Rest.Request<null> = {
method: 'DELETE',
url: '/api/some/path/to/foo/' + id,
};
return this.rest.request<null, void>(request, { skipHandleError: true });
}
```
`skipHandleError` config option, when set to `true`, disables the error handler and the returned observable starts throwing an error that you can catch in your subscription.
```js
removeFooFromList(id: number) {
this.demoService.deleteFoo(id).subscribe(
foo => {
// Do something with foo.
},
error => {
// Do something with error.
}
)
}
```
### How to Get a Specific API Endpoint From Application Config
Another nice config option that `request` method receives is `apiName` (available as of v2.4), which can be used to get a specific module endpoint from application configuration.
```js
putFoo(body: Foo, id: string) {
const request: Rest.Request<Foo> = {
method: 'PUT',
url: '/' + id,
body
};
return this.rest.request<Foo, void>(request, {apiName: 'foo'});
}
```
`putFoo` above will request `https://localhost:44305/api/some/path/to/foo/{id}` as long as the environment variables are as follows:
```js
// environment.ts
export const environment = {
apis: {
default: {
url: 'https://localhost:44305',
},
foo: {
url: 'https://localhost:44305/api/some/path/to/foo',
},
},
/* rest of the environment variables here */
}
```
### How to Observe Response Object or HTTP Events Instead of Body
`RestService` assumes you are generally interested in the body of a response and, by default, sets `observe` property as `'body'`. However, there may be times you are rather interested in something else, such as a custom proprietary header. For that, the `request` method receives `observe` property in its config object.
```js
getSomeCustomHeaderValue() {
const request: Rest.Request<null> = {
method: 'GET',
url: '/api/some/path/that/sends/some-custom-header',
};
return this.rest.request<null, HttpResponse<any>>(
request,
{observe: Rest.Observe.Response},
).pipe(
map(response => response.headers.get('Some-Custom-Header'))
);
}
```
You may find `Rest.Observe` enum [here](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/rest.ts#L10).
## What's Next?
* [Localization](./Localization.md)

6
docs/en/UI/Angular/Localization.md

@ -133,4 +133,8 @@ Localization resources are stored in the `localization` property of `ConfigState
## See Also ## See Also
* [Localization in ASP.NET Core](../../Localization.md) * [Localization in ASP.NET Core](../../Localization.md)
## What's Next?
* [Permission Management](./Permission-Management.md)

2
docs/en/UI/Angular/Permission-Management.md

@ -76,4 +76,4 @@ Granted Policies are stored in the `auth` property of `ConfigState`.
## What's Next? ## What's Next?
* [Component Replacement](./Component-Replacement.md) * [Config State](./Config-State.md)

67
docs/en/UI/Angular/Service-Proxies.md

@ -0,0 +1,67 @@
## Service Proxies
It is common to call a REST endpoint in the server from our Angular applications. In this case, we generally create **services** (those have methods for each service method on the server side) and **model objects** (matches to [DTOs](../../Data-Transfer-Objects) in the server side).
In addition to manually creating such server-interacting services, we could use tools like [NSWAG](https://github.com/RicoSuter/NSwag) to generate service proxies for us. But NSWAG has the following problems we've experienced:
* It generates a **big, single** .ts file which has some problems;
* It get **too large** when your application grows.
* It doesn't fit into the **[modular](../../Module-Development-Basics) approach** of the ABP framework.
* It creates a bit **ugly code**. We want to have a clean code (just like if we write manually).
* It can not generate the same **method signature** declared in the server side (because swagger.json doesn't exactly reflect the method signature of the backend service). We've created an endpoint that exposes server side method contacts to allow clients generate a better aligned client proxies.
ABP CLI `generate-proxies` command automatically generates the typescript client proxies by creating folders which separated by module names in the `src/app` folder.
Run the following command in the **root folder** of the angular application:
```bash
abp generate-proxy
```
It only creates proxies only for your own application's services. It doesn't create proxies for the services of the application modules you're using (by default). There are several options. See the [CLI documentation](../../CLI).
The files generated with the `--module all` option like below:
![generated-files-via-generate-proxy](./images/generated-files-via-generate-proxy.png)
### Services
Each generated service matches a back-end controller. The services methods call back-end APIs via [RestService](./Http-Requests#restservice).
A variable named `apiName` (available as of v2.4) is defined in each service. `apiName` matches the module's RemoteServiceName. This variable passes to the `RestService` as a parameter at each request. If there is no microservice API defined in the environment, `RestService` uses the default. See [getting a specific API endpoint from application config](./Http-Requests#how-to-get-a-specific-api-endpoint-from-application-config)
The `providedIn` property of the services is defined as `'root'`. Therefore no need to add a service as a provider to a module. You can use a service by injecting it into a constructor as shown below:
```js
import { AbpApplicationConfigurationService } from '../app/shared/services';
//...
export class HomeComponent{
constructor(private appConfigService: AbpApplicationConfigurationService) {}
ngOnInit() {
this.appConfigService.get().subscribe()
}
}
```
The Angular compiler removes the services that have not been injected anywhere from the final output. See the [tree-shakable providers documentation](https://angular.io/guide/dependency-injection-providers#tree-shakable-providers).
### Models
The generated models match the DTOs in the back-end. Each model is generated as a class under the `src/app/*/shared/models` folder.
There are a few [base classes](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/dtos.ts) in the `@abp/ng.core` package. Some models extend these classes.
A class instance can be created as shown below:
```js
import { IdentityRoleCreateDto } from '../identity/shared/models';
//...
const instance = new IdentityRoleCreateDto({name: 'Role 1', isDefault: false, isPublic: true})
```
Initial values ​​can optionally be passed to each class constructor.
## What's Next?
* [HTTP Requests](./Http-Requests)

113
docs/en/UI/Angular/Track-By-Service.md

@ -0,0 +1,113 @@
# Easy TrackByFunction Implementation
`TrackByService` is a utility service to provide an easy implementation for one of the most frequent needs in Angular templates: `TrackByFunction`. Please see [this page in Angular docs](https://angular.io/guide/template-syntax#ngfor-with-trackby) for its purpose.
## Getting Started
You do not have to provide the `TrackByService` at module or component level, because it is already **provided in root**. You can inject and start using it immediately in your components. For better type support, you may pass in the type of the iterated item to it.
```js
import { TrackByService } from '@abp/ng.core';
@Component({
/* class metadata here */
})
class DemoComponent {
list: Item[];
constructor(public readonly track: TrackByService<Item>) {}
}
```
> Noticed `track` is `public` and `readonly`? That is because we will see some examples where methods of `TrackByService` instance are directly called in the component's template. That may be considered as an anti-pattern, but it has its own advantage, especially when component inheritance is leveraged. You can always use public component properties instead.
**The members are also exported as separate functions.** If you do not want to inject `TrackByService`, you can always import and use those functions directly in your classes.
## Usage
There are two approaches available.
1. You may inject `TrackByService` to your component and use its members.
2. You may use exported higher-order functions directly on component properties.
### How to Track Items by a Key
You can use `by` to get a `TrackByFunction` that tracks the iterated object based on one of its keys. For type support, you may pass in the type of the iterated item to it.
```html
<!-- template of DemoComponent -->
<div *ngFor="let item of list; trackBy: track.by('id')">{%{{{ item.name }}}%}</div>
```
`by` is exported as a stand-alone function and is named `trackBy`.
```js
import { trackBy } from "@abp/ng.core";
@Component({
template: `
<div
*ngFor="let item of list; trackBy: trackById"
>
{%{{{ item.name }}}%}
</div>
`,
})
class DemoComponent {
list: Item[];
trackById = trackBy<Item>('id');
}
```
### How to Track by a Deeply Nested Key
You can use `byDeep` to get a `TrackByFunction` that tracks the iterated object based on a deeply nested key. For type support, you may pass in the type of the iterated item to it.
```html
<!-- template of DemoComponent -->
<div
*ngFor="let item of list; trackBy: track.byDeep('tenant', 'account', 'id')"
>
{%{{{ item.tenant.name }}}%}
</div>
```
`byDeep` is exported as a stand-alone function and is named `trackByDeep`.
```js
import { trackByDeep } from "@abp/ng.core";
@Component({
template: `
<div
*ngFor="let item of list; trackBy: trackByTenantAccountId"
>
{%{{{ item.name }}}%}
</div>
`,
})
class DemoComponent {
list: Item[];
trackByTenantAccountId = trackByDeep<Item>('tenant', 'account', 'id');
}
```

BIN
docs/en/UI/Angular/images/generated-files-via-generate-proxy.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

3
docs/en/UI/AspNetCore/Libraries/DatatablesNet.md

@ -0,0 +1,3 @@
# ABP Datatables.Net Integration for ASP.NET Core UI
TODO

92
docs/en/UI/AspNetCore/Tag-Helpers/Collapse.md

@ -0,0 +1,92 @@
# Collapse
## Introduction
`abp-collapse-body` is the main container for showing and hiding content. `abp-collapse-id` is used to show and hide the content container. Can be triggered with both `abp-button` and `a` tags.
Basic usage:
````xml
<abp-button button-type="Primary" abp-collapse-id="collapseExample" text="Button with data-target" />
<a abp-button="Primary" abp-collapse-id="collapseExample"> Link with href </a>
<abp-collapse-body id="collapseExample">
Anim pariatur wolf moon tempor,,, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.
</abp-collapse-body>
````
## Demo
See the [collapse demo page](https://bootstrap-taghelpers.abp.io/Components/Collapse) to see it in action.
## Attributes
### show
A value indicates if the collapse body will be initialized visible or hidden. Should be one of the following values:
* `false` (default value)
* `true`
### multi
A value indicates if an `abp-collapse-body` can be shown or hidden by an element that can show/hide multiple collapse bodies. Basically, this attribute adds "multi-collapse" class to `abp-collapse-body`. Should be one of the following values:
* `false` (default value)
* `true`
Sample:
````xml
<a abp-button="Primary" abp-collapse-id="FirstCollapseExample"> Toggle first element </a>
<abp-button button-type="Primary" abp-collapse-id="SecondCollapseExample" text="Toggle second element" />
<abp-button button-type="Primary" abp-collapse-id="FirstCollapseExample SecondCollapseExample" text="Toggle both elements" />
<abp-row class="mt-3">
<abp-column size-sm="_6">
<abp-collapse-body id="FirstCollapseExample" multi="true">
Curabitur porta porttitor libero eu luctus. Praesent ultrices mattis commodo. Integer sodales massa risus, in molestie enim sagittis blandit
</abp-collapse-body>
</abp-column>
<abp-column size-sm="_6">
<abp-collapse-body id="SecondCollapseExample" multi="true">
Anim pariatur wolf moon tempor,,, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et.
</abp-collapse-body>
</abp-column>
</abp-row>
````
## Accordion example
`abp-accordion` is the main container for the accordion items.
Basic usage:
````xml
<abp-accordion>
<abp-accordion-item title="Collapsible Group Item #1">
Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry rtat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.
</abp-accordion-item>
<abp-accordion-item title="Collapsible Group Item #2">
Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.
</abp-accordion-item>
<abp-accordion-item title="Collapsible Group Item #3">
Anim pariatur wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.
</abp-accordion-item>
</abp-accordion>
````
## Attributes
### active
A value indicates if the accordion item will be initialized visible or hidden. Should be one of the following values:
* `false` (default value)
* `true`
### title
A value indicates the visible title of the accordion item. Should be a string value.

97
docs/en/UI/AspNetCore/Tag-Helpers/Dropdowns.md

@ -0,0 +1,97 @@
# Dropdowns
## Introduction
`abp-dropdown` is the main container for dropdown content.
Basic usage:
````xml
<abp-dropdown>
<abp-dropdown-button text="Dropdown button" />
<abp-dropdown-menu>
<abp-dropdown-item href="#">Action</abp-dropdown-item>
<abp-dropdown-item href="#">Another action</abp-dropdown-item>
<abp-dropdown-item href="#">Something else here</abp-dropdown-item>
</abp-dropdown-menu>
</abp-dropdown>
````
## Demo
See the [dropdown demo page](https://bootstrap-taghelpers.abp.io/Components/Dropdowns) to see it in action.
## Attributes
### direction
A value indicates which direction the dropdown buttons will be displayed to. Should be one of the following values:
* `Down` (default value)
* `Up`
* `Right`
* `Left`
### dropdown-style
A value indicates if an `abp-dropdown-button` will have split icon for dropdown. Should be one of the following values:
* `Single` (default value)
* `Split`
## Menu items
`abp-dropdown-menu` is the main container for dropdown menu items.
Basic usage:
````xml
<abp-dropdown>
<abp-dropdown-button button-type="Secondary" text="Dropdown"/>
<abp-dropdown-menu>
<abp-dropdown-header>Dropdown Header</abp-dropdown-header>
<abp-dropdown-item href="#">Action</abp-dropdown-item>
<abp-dropdown-item active="true" href="#">Active action</abp-dropdown-item>
<abp-dropdown-item disabled="true" href="#">Disabled action</abp-dropdown-item>
<abp-dropdown-divider/>
<abp-dropdown-item-text>Dropdown Item Text</abp-dropdown-item-text>
<abp-dropdown-item href="#">Something else here</abp-dropdown-item>
</abp-dropdown-menu>
</abp-dropdown>
````
## Attributes
### align
A value indicates which direction `abp-dropdown-menu` items will be aligned to. Should be one of the following values:
* `Left` (default value)
* `Right`
### Additional content
`abp-dropdown-menu` can also contain additional HTML elements like headings, paragraphs, dividers or form element.
Example:
````xml
<abp-dropdown >
<abp-dropdown-button button-type="Secondary" text="Dropdown With Form"/>
<abp-dropdown-menu>
<form class="px-4 py-3">
<abp-input asp-for="EmailAddress"></abp-input>
<abp-input asp-for="Password"></abp-input>
<abp-input asp-for="RememberMe"></abp-input>
<abp-button button-type="Primary" text="Sign In" type="submit" />
</form>
<abp-dropdown-divider></abp-dropdown-divider>
<abp-dropdown-item href="#">New around here? Sign up</abp-dropdown-item>
<abp-dropdown-item href="#">Forgot password?</abp-dropdown-item>
</abp-dropdown-menu>
</abp-dropdown>
````

10
docs/en/UI/AspNetCore/Tag-Helpers/Index.md

@ -15,6 +15,16 @@ Here, the list of components those are wrapped by the ABP Framework:
* [Buttons](Buttons.md) * [Buttons](Buttons.md)
* [Cards](Cards.md) * [Cards](Cards.md)
* [Alerts](Alerts.md) * [Alerts](Alerts.md)
* [Tabs](Tabs.md)
* [Grids](Grids.md)
* [Modals](Modals.md)
* [Collapse](Collapse.md)
* [Dropdowns](Dropdowns.md)
* [List Groups](List-Groups.md)
* [Paginator](Paginator.md)
* [Popovers](Popovers.md)
* [Progress Bars](Progress-Bars.md)
* [Tooltips](Tooltips.md)
* ... * ...
> Until all the tag helpers are documented, you can visit https://bootstrap-taghelpers.abp.io/ to see them with live samples. > Until all the tag helpers are documented, you can visit https://bootstrap-taghelpers.abp.io/ to see them with live samples.

78
docs/en/UI/AspNetCore/Tag-Helpers/List-Groups.md

@ -0,0 +1,78 @@
# List Groups
## Introduction
`abp-list-group` is the main container for list group content.
Basic usage:
````xml
<abp-list-group>
<abp-list-group-item>Cras justo odio</abp-list-group-item>
<abp-list-group-item>Dapibus ac facilisis in</abp-list-group-item>
<abp-list-group-item>Morbi leo risus</abp-list-group-item>
<abp-list-group-item>Vestibulum at eros</abp-list-group-item>
</abp-list-group>
````
## Demo
See the [list groups demo page](https://bootstrap-taghelpers.abp.io/Components/ListGroups) to see it in action.
## Attributes
### flush
A value indicates `abp-list-group` items to remove some borders and rounded corners to render list group items edge-to-edge in a parent container. Should be one of the following values:
* `false` (default value)
* `true`
### active
A value indicates if an `abp-list-group-item` to be active. Should be one of the following values:
* `false` (default value)
* `true`
### disabled
A value indicates if an `abp-list-group-item` to be disabled. Should be one of the following values:
* `false` (default value)
* `true`
### href
A value indicates if an `abp-list-group-item` has a link. Should be a string link value.
### type
A value indicates an `abp-list-group-item` style class with a stateful background and color. Should be one of the following values:
* `Default` (default value)
* `Primary`
* `Secondary`
* `Success`
* `Danger`
* `Warning`
* `Info`
* `Light`
* `Dark`
* `Link`
### Additional content
`abp-list-group-item` can also contain additional HTML elements like spans.
Example:
````xml
<abp-list-group>
<abp-list-group-item>Cras justo odio <span abp-badge-pill="Primary">14</span></abp-list-group-item>
<abp-list-group-item>Dapibus ac facilisis in <span abp-badge-pill="Primary">2</span></abp-list-group-item>
<abp-list-group-item>Morbi leo risus <span abp-badge-pill="Primary">1</span></abp-list-group-item>
</abp-list-group>
````

81
docs/en/UI/AspNetCore/Tag-Helpers/Modals.md

@ -0,0 +1,81 @@
# Modals
## Introduction
`abp-modal` is a main element to create a modal.
Basic usage:
````xml
<abp-button button-type="Primary" data-toggle="modal" data-target="#myModal">Launch modal</abp-button>
<abp-modal centered="true" size="Large" id="myModal">
<abp-modal-header title="Modal title"></abp-modal-header>
<abp-modal-body>
Woohoo, you're reading this text in a modal!
</abp-modal-body>
<abp-modal-footer buttons="Close"></abp-modal-footer>
</abp-modal>
````
## Demo
See the [modals demo page](https://bootstrap-taghelpers.abp.io/Components/Modals) to see it in action.
## Attributes
### centered
A value indicates the positioning of the modal. Should be one of the following values:
* `false` (default value)
* `true`
### size
A value indicates the size of the modal. Should be one of the following values:
* `Default` (default value)
* `Small`
* `Large`
* `ExtraLarge`
### static
A value indicates if the modal will be static. Should be one of the following values:
* `false` (default value)
* `true`
### Additional content
`abp-modal-footer` can have multiple buttons with alignment option.
Add `@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal` to your page.
Example:
````xml
<abp-button button-type="Primary" data-toggle="modal" data-target="#myModal">Launch modal</abp-button>
<abp-modal centered="true" size="Large" id="myModal" static="true">
<abp-modal-header title="Modal title"></abp-modal-header>
<abp-modal-body>
Woohoo, you're reading this text in a modal!
</abp-modal-body>
<abp-modal-footer buttons="@(AbpModalButtons.Save|AbpModalButtons.Close)" button-alignment="Between"></abp-modal-footer>
</abp-modal>
````
### button-alignment
A value indicates the positioning of your modal footer buttons. Should be one of the following values:
* `Default` (default value)
* `Start`
* `Center`
* `Around`
* `Between`
* `End`

57
docs/en/UI/AspNetCore/Tag-Helpers/Paginator.md

@ -0,0 +1,57 @@
# Paginator
## Introduction
`abp-paginator` is the abp tag for pagination. Requires `Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination.PagerModel` type of model.
Basic usage:
````xml
<abp-paginator model="Model.PagerModel" show-info="true"></abp-paginator>
````
Model:
````xml
using Microsoft.AspNetCore.Mvc.RazorPages;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components
{
public class PaginatorModel : PageModel
{
public PagerModel PagerModel { get; set; }
public void OnGet(int currentPage, string sort)
{
PagerModel = new PagerModel(100, 10, currentPage, 10, "Paginator", sort);
}
}
}
````
## Demo
See the [paginator demo page](https://bootstrap-taghelpers.abp.io/Components/Paginator) to see it in action.
## Attributes
### model
`Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination.PagerModel` type of model can be initialized with the following data:
* `totalCount`
* `shownItemsCount`
* `currentPage`
* `pageSize`
* `pageUrl`
* `sort` (default null)
### show-info
A value indicates if an extra information about start, end and total records will be displayed. Should be one of the following values:
* `false` (default value)
* `true`

70
docs/en/UI/AspNetCore/Tag-Helpers/Progress-Bars.md

@ -0,0 +1,70 @@
# Progress Bars
## Introduction
`abp-progress-bar` is the abp tag for progress bar status.
Basic usage:
````xml
<abp-progress-bar value="70" />
<abp-progress-bar type="Warning" value="25"> %25 </abp-progress-bar>
<abp-progress-bar type="Success" value="40" strip="true"/>
<abp-progress-bar type="Dark" value="10" min-value="5" max-value="15" strip="true"> %50 </abp-progress-bar>
<abp-progress-group>
<abp-progress-part type="Success" value="25"/>
<abp-progress-part type="Danger" value="10" strip="true"> %10 </abp-progress-part>
<abp-progress-part type="Primary" value="50" animation="true" strip="true" />
</abp-progress-group>
````
## Demo
See the [progress bars demo page](https://bootstrap-taghelpers.abp.io/Components/Progress-Bars) to see it in action.
## Attributes
### value
A value indicates the current progress of the bar.
### type
A value indicates the background color of the progress bar. Should be one of the following values:
* `Default` (default value)
* `Secondary`
* `Success`
* `Danger`
* `Warning`
* `Info`
* `Light`
* `Dark`
### min-value
Minimum value of the progress bar. Default is 0.
### max-value
Maximum value of the progress bar. Default is 100.
### strip
A value indicates if the background style of the progress bar is stripped. Should be one of the following values:
* `false` (default value)
* `true`
### animation
A value indicates if the stripped background style of the progress bar is animated. Should be one of the following values:
* `false` (default value)
* `true`

2
docs/en/UI/AspNetCore/Tag-Helpers/Tabs.md

@ -30,7 +30,7 @@ Basic usage:
## Demo ## Demo
See the [cards demo page](https://bootstrap-taghelpers.abp.io/Components/Cards) to see it in action. See the [tabs demo page](https://bootstrap-taghelpers.abp.io/Components/Tabs) to see it in action.
## abp-tab Attributes ## abp-tab Attributes

35
docs/en/UI/AspNetCore/Tag-Helpers/Tooltips.md

@ -0,0 +1,35 @@
# Tooltips
## Introduction
`abp-tooltip` is the abp tag for tooltips.
Basic usage:
````xml
<abp-button abp-tooltip="Tooltip">
Tooltip Default
</abp-button>
<abp-button abp-tooltip-top="Tooltip">
Tooltip on top
</abp-button>
<abp-button abp-tooltip-right="Tooltip">
Tooltip on right
</abp-button>
<abp-button abp-tooltip-bottom="Tooltip">
Tooltip on bottom
</abp-button>
<abp-button disabled="true" abp-tooltip="Tooltip">
Disabled button Tooltip
</abp-button>
````
## Demo
See the [tooltips demo page](https://bootstrap-taghelpers.abp.io/Components/Tooltips) to see it in action.

1312
docs/en/UI/Common/Utils/Linked-List.md

File diff suppressed because it is too large

26
docs/en/docs-nav.json

@ -5,6 +5,7 @@
"items": [ "items": [
{ {
"text": "From Startup Templates", "text": "From Startup Templates",
"path": "Getting-Started-With-Startup-Templates.md",
"items": [ "items": [
{ {
"text": "Application with MVC (Razor Pages) UI", "text": "Application with MVC (Razor Pages) UI",
@ -302,12 +303,24 @@
{ {
"text": "Theming", "text": "Theming",
"path": "UI/AspNetCore/Theming.md" "path": "UI/AspNetCore/Theming.md"
},
{
"text": "Customize/Extend the UI",
"path": "UI/AspNetCore/Customization-User-Interface.md"
} }
] ]
}, },
{ {
"text": "Angular", "text": "Angular",
"items": [ "items": [
{
"text": "Service Proxies",
"path": "UI/Angular/Service-Proxies.md"
},
{
"text": "HTTP Requests",
"path": "UI/Angular/HTTP-Requests.md"
},
{ {
"text": "Localization", "text": "Localization",
"path": "UI/Angular/Localization.md" "path": "UI/Angular/Localization.md"
@ -327,6 +340,19 @@
{ {
"text": "Custom Setting Page", "text": "Custom Setting Page",
"path": "UI/Angular/Custom-Setting-Page.md" "path": "UI/Angular/Custom-Setting-Page.md"
},
{
"text": "TrackByService",
"path": "UI/Angular/Track-By-Service.md"
}
]
},
{
"text": "Common",
"items": [
{
"text": "Linked List (Doubly)",
"path": "UI/Common/Utils/Linked-List.md"
} }
] ]
} }

7
docs/zh-Hans/Best-Practices/Entity-Framework-Core-Integration.md

@ -89,20 +89,23 @@ public static class IdentityDbContextModelBuilderExtensions
builder.Entity<IdentityUser>(b => builder.Entity<IdentityUser>(b =>
{ {
b.ToTable(options.TablePrefix + "Users", options.Schema); b.ToTable(options.TablePrefix + "Users", options.Schema);
b.ConfigureByConvention();
//code omitted for brevity //code omitted for brevity
}); });
builder.Entity<IdentityUserClaim>(b => builder.Entity<IdentityUserClaim>(b =>
{ {
b.ToTable(options.TablePrefix + "UserClaims", options.Schema); b.ToTable(options.TablePrefix + "UserClaims", options.Schema);
b.ConfigureByConvention();
//code omitted for brevity //code omitted for brevity
}); });
//code omitted for brevity //code omitted for brevity
} }
} }
```` ````
* **推荐** 为每个Enttiy映射调用 `b.ConfigureByConvention();`(如上所示).
* **推荐** 通过继承 `ModelBuilderConfigurationOptions` 来创建 **configuration Options** 类. 例如: * **推荐** 通过继承 `ModelBuilderConfigurationOptions` 来创建 **configuration Options** 类. 例如:
````C# ````C#

24
docs/zh-Hans/CLI.md

@ -22,7 +22,7 @@ dotnet tool update -g Volo.Abp.Cli
生成基于ABP[启动模板](Startup-Templates/Index.md)的新解决方案. 生成基于ABP[启动模板](Startup-Templates/Index.md)的新解决方案.
基本用法: 用法:
````bash ````bash
abp new <解决方案名称> [options] abp new <解决方案名称> [options]
@ -70,7 +70,7 @@ abp new Acme.BookStore
> 需要注意的是添加的模块可能需要额外的配置,通常会在包的文档中指出. > 需要注意的是添加的模块可能需要额外的配置,通常会在包的文档中指出.
基本用法: 用法:
````bash ````bash
abp add-package <包名> [options] abp add-package <包名> [options]
@ -94,7 +94,7 @@ abp add-package Volo.Abp.MongoDB
> 由于分层,不同的数据库提供程序选项或其他原因,业务模块通常由多个包组成. 使用`add-module`命令可以大大简化向模块添加模块的过程. 但是每个模块可能需要一些其他配置,这些配置通常在相关模块的文档中指出. > 由于分层,不同的数据库提供程序选项或其他原因,业务模块通常由多个包组成. 使用`add-module`命令可以大大简化向模块添加模块的过程. 但是每个模块可能需要一些其他配置,这些配置通常在相关模块的文档中指出.
基本用法: 用法:
````bash ````bash
abp add-module <模块名称> [options] abp add-module <模块名称> [options]
@ -174,26 +174,26 @@ abp logout
### generate-proxy ### generate-proxy
生成typescript服务和DTO代理 为你的HTTP API生成客户端代码,简化客户端使用服务的成本. 在运行 `generate-proxy` 命令之前,你的host必须启动正在运行.
基本用法: 用法:
````bash ````bash
abp generate-proxy [options] abp generate-proxy [options]
```` ````
#### Options
* `--apiUrl` 或者 `-a`:指定HTTP API的根URL. 如果未指定这个选项,默认使用你Angular应用程序的`environment.ts`文件API URL. 在运行 `generate-proxy` 命令之前,你的host必须启动正在运行.
* `--ui` 或者 `-u`: 指定UI框架,默认框架是angular.当前只有angular一个选项, 但我们会通过更改CLI增加新的选项. 尽请关注!
* `--module` 或者 `-m`:指定模块名. 默认模块名称为app. 如果你想所有模块,你可以指定 `--module all` 命令.
示例: 示例:
````bash ````bash
abp generate-proxy --apiUrl https://localhost:44305 --ui angular --module all abp generate-proxy --apiUrl https://localhost:44305 --ui angular --module all
```` ````
#### Options
* `--apiUrl` 或者 `-a`:如果未指定这个选项,默认使用你的environment.ts文件的API URL, 你可以随时使用这个选项指定API源.
* `--ui` 或者 `-u`: 指定UI框架,默认框架是angular.当前只有angular一个选项, 但我们会通过更改CLI增加新的选项. 尽请关注!
* `--module` 或者 `-m`:指定模块名. 默认模块名称为app. 如果你想所有模块,你可以指定 `--module all` 命令.
### help ### help
CLI的基本用法信息. CLI的基本用法信息.

31
docs/zh-Hans/Customizing-Application-Modules-Extending-Entities.md

@ -25,7 +25,36 @@ return user.GetProperty<string>("Title");
参阅[实体文档](Entities.md)了解更多关于额外系统. 参阅[实体文档](Entities.md)了解更多关于额外系统.
> 可以基于额外的属性执行**业务逻辑**. 你可以**override**服务方法获取或设置值. 重写服务在下面进行讨论. > 可以基于额外的属性执行**业务逻辑**. 你可以[重写服务方法](Customizing-Application-Modules-Overriding-Services.md). 然后获取或设置如上所示的值.
## 实体扩展 (EF Core)
如上所述,实体所有的额外属性都作为单个JSON对象存储在数据库表中. 它不适用复杂的场景,特别是在你需要的时候.
* 使用额外属性创建**索引**和**外键**.
* 使用额外属性编写**SQL**或**LINQ**(例如根据属性值搜索).
* 创建你**自己的实体**映射到相同的表,但在实体中定义一个额外属性做为 **常规属性**(参阅 [EF Core迁移文档](Entity-Framework-Core-Migrations.md)了解更多).
为了解决上面的问题,用于EF Core的ABP框架实体扩展系统允许你使用上面定义相同的额外属性API,但将所需的属性存储在单独的数据库表字段中.
假设你想要添加 `SocialSecurityNumber` 到[身份模块](Modules/Identity.md)的 `IdentityUser` 实体. 你可以使用 `ObjectExtensionManager` 类:
````csharp
ObjectExtensionManager.Instance
.MapEfCoreProperty<IdentityUser, string>(
"SocialSecurityNumber",
b => { b.HasMaxLength(32); }
);
````
* 你提供了 `IdentityUser` 作为实体名(泛型参数), `string` 做为新属性的类型, `SocialSecurityNumber` 做为属性名(也是数据库表的字段名).
* 你还需要提供一个使用[EF Core Fluent API](https://docs.microsoft.com/en-us/ef/core/modeling/entity-properties)定义数据库映射属性的操作.
> 必须在使用相关的 `DbContext` 之前执行此代码. 应用程序启动模板定义了一个名为 `YourProjectNameEntityExtensions` 的静态类. 你可以在此类中定义扩展确保在正确的时间执行它. 否则你需要自己处理.
定义实体扩展后你需要使用EF Core的[Add-Migration](https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/powershell#add-migration)和[Update-Database](https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/powershell#update-database)命令来创建code first迁移类并更新数据库.
然后你可以使用上一部分中定义的相同额外属性系统来操纵实体上的属性.
## 创建新实体映射到同一个数据库表/Collection ## 创建新实体映射到同一个数据库表/Collection

167
docs/zh-Hans/Customizing-Application-Modules-Overriding-Services.md

@ -1,3 +1,166 @@
# 自定义应用模块: 覆盖服务 # 自定义应用模块: 重写服务
TODO... 你可能想要**更改**依赖模块的**行为(业务逻辑)**. 在这种情况下,你可以使用[依赖注入](Dependency-Injection.md)的能力替换服务,控制器甚至页面模型到你自己的实现.
注册到依赖注入的任何类,包括ABP框架的服务都可以被**替换**.
你可以根据自己的需求使用不同的选项,下面的章节中将介绍这些选项.
> 请注意,某些服务方法可能不是virtual,你可能无法override,我们会通过设计将其virtual,如果你发现任何方法不可以被覆盖,请[创建一个issue](https://github.com/abpframework/abp/issues/new)或者你直接修改后并发送**pull request**到GitHub.
## 替换接口
如果给定的服务定义了接口,像 `IdentityUserAppService` 类实现了 `IIdentityUserAppService` 接口,你可以为这个接口创建自己的实现并且替换当前的实现. 例如:
````csharp
public class MyIdentityUserAppService : IIdentityUserAppService, ITransientDependency
{
//...
}
````
`MyIdentityUserAppService` 通过命名约定替换了 `IIdentityUserAppService` 的当前实现. 如果你的类名不匹配,你需要手动公开服务接口:
````csharp
[ExposeServices(typeof(IIdentityUserAppService))]
public class TestAppService : IIdentityUserAppService, ITransientDependency
{
//...
}
````
依赖注入系统允许为一个接口注册多个服务. 注入接口时会解析最后一个注入的服务. 显式的替换服务是一个好习惯.
示例:
````csharp
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IIdentityUserAppService))]
public class TestAppService : IIdentityUserAppService, ITransientDependency
{
//...
}
````
使用这种方法, `IIdentityUserAppService` 接口将只会有一个实现. 也可以使用以下方法替换服务:
````csharp
context.Services.Replace(
ServiceDescriptor.Transient<IIdentityUserAppService, MyIdentityUserAppService>()
);
````
你可以在[模块](Module-Development-Basics.md)类的 `ConfigureServices` 方法编写替换服务代码.
## 重写一个服务类
大多数情况下,你会仅想改变服务当前实现的一个或几个方法. 重新实现完整的接口变的繁琐,更好的方法是继承原始类并重写方法。
### 示例: 重写服务方法
````csharp
[Dependency(ReplaceServices = true)]
public class MyIdentityUserAppService : IdentityUserAppService
{
//...
public MyIdentityUserAppService(
IdentityUserManager userManager,
IIdentityUserRepository userRepository,
IGuidGenerator guidGenerator
) : base(
userManager,
userRepository,
guidGenerator)
{
}
public override async Task<IdentityUserDto> CreateAsync(IdentityUserCreateDto input)
{
if (input.PhoneNumber.IsNullOrWhiteSpace())
{
throw new AbpValidationException(
"Phone number is required for new users!",
new List<ValidationResult>
{
new ValidationResult(
"Phone number can not be empty!",
new []{"PhoneNumber"}
)
}
); }
return await base.CreateAsync(input);
}
}
````
示例中**重写**了 `IdentityUserAppService` [应用程序](Application-Services.md) `CreateAsync` 方法检查手机号码. 然后调用了基类方法继续**基本业务逻辑**. 通过这种方法你可以在基本业务逻辑**之前**和**之后**执行其他业务逻辑.
你也可以完全**重写**整个业务逻辑去创建用户,而不是调用基类方法.
### 示例: 重写领域服务
````csharp
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IdentityUserManager))]
public class MyIdentityUserManager : IdentityUserManager
{
public MyIdentityUserManager(
IdentityUserStore store,
IOptions<IdentityOptions> optionsAccessor,
IPasswordHasher<IdentityUser> passwordHasher,
IEnumerable<IUserValidator<IdentityUser>> userValidators,
IEnumerable<IPasswordValidator<IdentityUser>> passwordValidators,
ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors,
IServiceProvider services,
ILogger<IdentityUserManager> logger,
ICancellationTokenProvider cancellationTokenProvider
) : base(
store,
optionsAccessor,
passwordHasher,
userValidators,
passwordValidators,
keyNormalizer,
errors,
services,
logger,
cancellationTokenProvider)
{
}
public override async Task<IdentityResult> CreateAsync(IdentityUser user)
{
if (user.PhoneNumber.IsNullOrWhiteSpace())
{
throw new AbpValidationException(
"Phone number is required for new users!",
new List<ValidationResult>
{
new ValidationResult(
"Phone number can not be empty!",
new []{"PhoneNumber"}
)
}
);
}
return await base.CreateAsync(user);
}
}
````
示例中类继承了 `IdentityUserManager` [领域服务](Domain-Services.md),并且重写了 `CreateAsync` 方法进行了与之前相同的手机号码检查. 结果也是一样的,但是这次我们在领域服务实现了它,假设这是我们系统的**核心领域逻辑**.
> 这里需要 `[ExposeServices(typeof(IdentityUserManager))]` attribute,因为 `IdentityUserManager` 没有定义接口 (像 `IIdentityUserManager`) ,依赖注入系统并不会按照约定公开继承类的服务(如已实现的接口).
参阅[本地化系统](Localization.md)了解如何自定义错误消息.
### 重写其他服务
控制器,框架服务,视图组件类以及其他类型注册到依赖注入的类都可以像上面的示例那样被重写.
## 如何找到服务?
[模块文档](Modules/Index.md) 包含了定义的主要服务列表. 另外 你也可以查看[源码](https://github.com/abpframework/abp/tree/dev/modules)找到所有的服务.

9
docs/zh-Hans/Entities.md

@ -365,14 +365,17 @@ public static class IdentityUserExtensions
存储字典的方式取决于你使用的数据库提供程序. 存储字典的方式取决于你使用的数据库提供程序.
* 对于 [Entity Framework Core](Entity-Framework-Core.md), 它以 `JSON` 字符串形式存储在 `ExtraProperties` 字段中. 序列化到 `JSON` 和反序列化到 `JSON` 由ABP使用EF Core的[值转换](https://docs.microsoft.com/zh-cn/ef/core/modeling/value-conversions)系统自动完成. * 对于 [Entity Framework Core](Entity-Framework-Core.md),这是两种类型的配置;
* 默认它以 `JSON` 字符串形式存储在 `ExtraProperties` 字段中. 序列化到 `JSON` 和反序列化到 `JSON` 由ABP使用EF Core的[值转换](https://docs.microsoft.com/zh-cn/ef/core/modeling/value-conversions)系统自动完成.
* 如果需要,你可以使用 `ObjectExtensionManager` 为所需的额外属性定义一个单独的数据库字段. 那些使用 `ObjectExtensionManager` 配置的属性继续使用单个 `JSON` 字段. 当你使用预构建的[应用模块](Modules/Index.md)并且想要[扩展模块的实体](Customizing-Application-Modules-Extending-Entities.md). 参阅[EF Core迁移文档](Entity-Framework-Core.md)了解如何使用 `ObjectExtensionManager`.
* 对于 [MongoDB](MongoDB.md), 它以 **常规字段** 存储, 因为 MongoDB 天生支持这种 [额外](https://mongodb.github.io/mongo-csharp-driver/1.11/serialization/#supporting-extra-elements) 系统. * 对于 [MongoDB](MongoDB.md), 它以 **常规字段** 存储, 因为 MongoDB 天生支持这种 [额外](https://mongodb.github.io/mongo-csharp-driver/1.11/serialization/#supporting-extra-elements) 系统.
### 讨论额外的属性 ### 讨论额外的属性
如果你使用**可重复使用的模块**,其中定义了一个实体,你想使用简单的方式get/set此实体相关的一些数据,那么额外的属性系统是非常有用的. 通常 **不需要** 为自己的实体使用这个系统,是因为它有以下缺点: 如果你使用**可重复使用的模块**,其中定义了一个实体,你想使用简单的方式get/set此实体相关的一些数据,那么额外的属性系统是非常有用的.
你通常 **不需要** 为自己的实体使用这个系统,是因为它有以下缺点:
* 它不是**完全类型安全的**. * 它不是**完全类型安全的**,因为它使用字符串用作属性名称.
* 这些属性**不容易[自动映射](Object-To-Object-Mapping.md)到其他对象**. * 这些属性**不容易[自动映射](Object-To-Object-Mapping.md)到其他对象**.
* 它**不会**为EF Core在数据库表中**创建字段**,因此在数据库中针对这个字段创建索引或搜索/排序并不容易. * 它**不会**为EF Core在数据库表中**创建字段**,因此在数据库中针对这个字段创建索引或搜索/排序并不容易.

100
docs/zh-Hans/Entity-Framework-Core-Migrations.md

@ -95,7 +95,7 @@ Volo.Abp.IdentityServer.AbpIdentityServerDbProperties.DbTablePrefix = "Ids";
这个项目有应用程序的 `DbContext`类(本例中的 `BookStoreDbContex` ). 这个项目有应用程序的 `DbContext`类(本例中的 `BookStoreDbContex` ).
**每个模块都使用自己的 `DbContext` 类**来访问数据库。同样你的应用程序有它自己的 `DbContext`. 通常在应用程序中使用这个 `DbContet`(如果你遵循最佳实践,应该在自定义[仓储](Repositories.md)中使用). 它几乎是一个空的 `DbContext`,因为你的应用程序在一开始没有任何实体,除了预定义的 `AppUser` 实体: **每个模块都使用自己的 `DbContext` 类**来访问数据库。同样你的应用程序有它自己的 `DbContext`. 通常在应用程序中使用这个 `DbContet`(如果你遵循最佳实践,应该在[仓储](Repositories.md)中使用). 它几乎是一个空的 `DbContext`,因为你的应用程序在一开始没有任何实体,除了预定义的 `AppUser` 实体:
````csharp ````csharp
[ConnectionStringName("Default")] [ConnectionStringName("Default")]
@ -119,15 +119,15 @@ public class BookStoreDbContext : AbpDbContext<BookStoreDbContext>
builder.Entity<AppUser>(b => builder.Entity<AppUser>(b =>
{ {
//Sharing the same table "AbpUsers" with the IdentityUser //Sharing the same Users table with the IdentityUser
b.ToTable("AbpUsers"); b.ToTable(AbpIdentityDbProperties.DbTablePrefix + "Users");
//Configure base properties
b.ConfigureByConvention(); b.ConfigureByConvention();
b.ConfigureAbpUser(); b.ConfigureAbpUser();
//Moved customization of the "AbpUsers" table to an extension method /* Configure mappings for your additional properties
b.ConfigureCustomUserProperties(); * Also see the MyProjectNameEntityExtensions class
*/
}); });
/* Configure your own tables/entities inside the ConfigureBookStore method */ /* Configure your own tables/entities inside the ConfigureBookStore method */
@ -190,12 +190,6 @@ public class BookStoreMigrationsDbContext : AbpDbContext<BookStoreMigrationsDbCo
builder.ConfigureFeatureManagement(); builder.ConfigureFeatureManagement();
builder.ConfigureTenantManagement(); builder.ConfigureTenantManagement();
/* Configure customizations for entities from the modules included */
builder.Entity<IdentityUser>(b =>
{
b.ConfigureCustomUserProperties();
});
/* Configure your own tables/entities inside the ConfigureBookStore method */ /* Configure your own tables/entities inside the ConfigureBookStore method */
builder.ConfigureBookStore(); builder.ConfigureBookStore();
} }
@ -276,7 +270,7 @@ public class BackgroundJobsDbContext
您可能想在应用程序中**重用依赖模块的表**. 在这种情况下你有两个选择: 您可能想在应用程序中**重用依赖模块的表**. 在这种情况下你有两个选择:
1. 你可以**直接使用模块定义的实体**. 1. 你可以**直接使用模块定义的实体**(你仍然可以在某种程度上[扩展实体](Customizing-Application-Modules-Extending-Entities.md)).
2. 你可以**创建一个新的实体**映射到同一个数据库表。 2. 你可以**创建一个新的实体**映射到同一个数据库表。
###### 使用由模块定义的实体 ###### 使用由模块定义的实体
@ -379,10 +373,8 @@ protected override void OnModelCreating(ModelBuilder builder)
builder.Entity<AppRole>(b => builder.Entity<AppRole>(b =>
{ {
b.ToTable("AbpRoles"); b.ToTable("AbpRoles");
b.ConfigureByConvention(); b.ConfigureByConvention();
b.Property(x => x.Title).HasMaxLength(128);
b.ConfigureCustomRoleProperties();
}); });
... ...
@ -399,68 +391,43 @@ protected override void OnModelCreating(ModelBuilder builder)
builder.Entity<AppRole>(b => builder.Entity<AppRole>(b =>
{ {
b.ToTable("AbpRoles"); b.ToTable("AbpRoles");
b.ConfigureByConvention(); b.ConfigureByConvention();
b.Property(x => x.Title).HasMaxLength(128);
b.ConfigureCustomRoleProperties();
}); });
```` ````
* 它映射到 `AbpRoles` 表,与 `IdentityRole` 实体共享. * 它映射到 `AbpRoles` 表,与 `IdentityRole` 实体共享.
* `ConfigureByConvention()` 配置了标准/基本属性(像`TenantId`),建议总是调用它. * `ConfigureByConvention()` 配置了标准/基本属性(像`TenantId`),建议总是调用它.
`ConfigureCustomRoleProperties()` 还不存在. 在 `BookStoreDbContextModelCreatingExtensions` 类中定义它 (在 `.EntityFrameworkCore` 项目的 `DbContext` 附近): 你已经为你的 `DbContext` 配置自定义属性,该属性在应用程序运行时使用.
与其直接更改 `MigrationsDbContext`,我们应该使用ABP框架的实体扩展系统,在解决方案的 `.EntityFrameworkCore` 项目中找到 `YourProjectNameEntityExtensions` 类(本示例中是 `BookStoreEntityExtensions`)并且进行以下更改:
````csharp ````csharp
public static void ConfigureCustomRoleProperties<TRole>(this EntityTypeBuilder<TRole> b) public static class MyProjectNameEntityExtensions
where TRole : class, IEntity<Guid>
{ {
b.Property<string>(nameof(AppRole.Title)).HasMaxLength(128); private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();
}
````
* 这个方法只定义实体的**自定义属性**.
* 遗憾的是,我们不能在这里充分的利用**类型安全**(通过引用`AppRole`实体). 我们能做的最好就是使用 `Title` 名称做为类型安全。
你已经为运行应用程序使用的 `DbContext` 配置了自定义属性. 我们还需要配置 `MigrationsDbContext`.
打开`MigrationsDbContext`(本例是 `BookStoreMigrationsDbContext`)进行以下更改:
````csharp
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
/* Include modules to your migration db context */
...
/* Configure customizations for entities from the modules included */
//CONFIGURE THE CUSTOM ROLE PROPERTIES public static void Configure()
builder.Entity<IdentityRole>(b =>
{ {
b.ConfigureCustomRoleProperties(); OneTimeRunner.Run(() =>
}); {
ObjectExtensionManager.Instance
... .MapEfCoreProperty<IdentityRole, string>(
"Title",
/* Configure your own tables/entities inside the ConfigureBookStore method */ builder => { builder.HasMaxLength(64); }
);
builder.ConfigureBookStore(); });
}
} }
```` ````
只增加下面几行: > 我们建议使用 `nameof(AppRole.Title)` 而不是硬编码 "Title" 字符串
````csharp `ObjectExtensionManager` 用于添加属性到现有的实体. 由于 `ObjectExtensionManager.Instance` 是静态实例(单例),因此应调用一次. `OneTimeRunner` 是ABP框架定义简单的工具类.
builder.Entity<IdentityRole>(b =>
{
b.ConfigureCustomRoleProperties();
});
````
通过这种方式,我们重用了用于为角色配置自定义属性映射的扩展方法. 但是对 `IdentityRole` 实体进行了相同的自定义. 参阅[EF Core集成文档](Entity-Framework-Core.md)了解更多关于实体扩展系统.
我们在两个类中都重复了类似的数据库映射代码,例如 `HasMaxLength(128)`.
现在你可以在包管理控制台(记得选择 `.EntityFrameworkCore.DbMigrations` 做为PMC的默认项目并将 `.Web` 项目设置为启动项目)使用标准的 `Add-Migration` 命令添加一个新的EF Core数据库迁移. 现在你可以在包管理控制台(记得选择 `.EntityFrameworkCore.DbMigrations` 做为PMC的默认项目并将 `.Web` 项目设置为启动项目)使用标准的 `Add-Migration` 命令添加一个新的EF Core数据库迁移.
@ -540,7 +507,7 @@ public class AppRoleAppService : ApplicationService, IAppRoleAppService
###### 使用ExtraProperties ###### 使用ExtraProperties
所有从 `AggregateRoot` 派生的实体都可以在 `ExtraProperties` 属性中存储键值对, 它是 `Dictionary<string, object>` 类型在数据库中被序列化为JSON. 所以你可以在字典中添加值用于查询,无需更改实体. 所有从 `AggregateRoot` 派生的实体都可以在 `ExtraProperties` 属性(因为它们都实现了 `IHasExtraProperties` 接口)中存储键值对, 它是 `Dictionary<string, object>` 类型在数据库中被序列化为JSON. 所以你可以在字典中添加值用于查询,无需更改实体.
例如你可以将查询属性 `Title` 存储在 `IdentityRole` 中,而不是创建一个新的实体. 例如你可以将查询属性 `Title` 存储在 `IdentityRole` 中,而不是创建一个新的实体.
例: 例:
@ -558,16 +525,13 @@ public class IdentityRoleExtendingService : ITransientDependency
public async Task<string> GetTitleAsync(Guid id) public async Task<string> GetTitleAsync(Guid id)
{ {
var role = await _identityRoleRepository.GetAsync(id); var role = await _identityRoleRepository.GetAsync(id);
return role.GetProperty<string>("Title"); return role.GetProperty<string>("Title");
} }
public async Task SetTitleAsync(Guid id, string newTitle) public async Task SetTitleAsync(Guid id, string newTitle)
{ {
var role = await _identityRoleRepository.GetAsync(id); var role = await _identityRoleRepository.GetAsync(id);
role.SetProperty("Title", newTitle); role.SetProperty("Title", newTitle);
await _identityRoleRepository.UpdateAsync(role); await _identityRoleRepository.UpdateAsync(role);
} }
} }
@ -580,6 +544,12 @@ public class IdentityRoleExtendingService : ITransientDependency
* 所有的额外属性都存储在数据库中的一个**JSON对象**,它们不是作为表的字段存储,与简单的表字段相比创建索引和针对此属性使用SQL查询将更加困难. * 所有的额外属性都存储在数据库中的一个**JSON对象**,它们不是作为表的字段存储,与简单的表字段相比创建索引和针对此属性使用SQL查询将更加困难.
* 属性名称是字符串,他们**不是类型安全的**. 建议这些类型的属性定义常量,以防止拼写错误. * 属性名称是字符串,他们**不是类型安全的**. 建议这些类型的属性定义常量,以防止拼写错误.
###### 使用实体扩展系统
实体扩展系统解决了额外属性主要的问题: 它可以将额外属性做为**标准表字段**存储到数据库.
你需要做的就是如上所诉使用 `ObjectExtensionManager` 定义额外属性, 然后你就可以使得 `GetProperty` 和 `SetProperty` 方法对实体的属性进行get/set,但是这时它存储在数据库表的单独字段中.
###### 创建新表 ###### 创建新表
你可以创建**自己的表**来存储属性,而不是创建新实体并映射到同一表. 你通常复制原始实体的一些值. 例如可以将 `Name` 字段添加到你自己的表中,它是原表中 `Name` 字段的副本. 你可以创建**自己的表**来存储属性,而不是创建新实体并映射到同一表. 你通常复制原始实体的一些值. 例如可以将 `Name` 字段添加到你自己的表中,它是原表中 `Name` 字段的副本.

108
docs/zh-Hans/Entity-Framework-Core.md

@ -58,6 +58,53 @@ namespace MyCompany.MyProject
} }
```` ````
### 关于EF Core Fluent Mapping
[应用程序启动模板](Startup-Templates/Application.md)已配置使用[EF Core fluent configuration API](https://docs.microsoft.com/en-us/ef/core/modeling/)映射你的实体到数据库表.
你依然为你的实体属性使用**data annotation attributes**(像`[Required]`),而ABP文档通常遵循**fluent mapping API** approach方法. 如何使用取决与你.
ABP框架有一些**实体基类**和**约定**(参阅[实体文档](Entities.md))提供了一些有用的扩展方法来配置从基本实体类继承的属性.
#### ConfigureByConvention 方法
`ConfigureByConvention()` 是主要的扩展方法,它对你的实体**配置所有的基本属性**和约定. 所以在你的流利映射代码中为你所有的实体调用这个方法是 **最佳实践**,
**示例**: 假设你有一个直接继承 `AggregateRoot<Guid>` 基类的 `Book` 实体:
````csharp
public class Book : AuditedAggregateRoot<Guid>
{
public string Name { get; set; }
}
````
你可以在你的 `DbContext` 重写 `OnModelCreating` 方法并且做以下配置:
````csharp
protected override void OnModelCreating(ModelBuilder builder)
{
//Always call the base method
base.OnModelCreating(builder);
builder.Entity<Book>(b =>
{
b.ToTable("Books");
//Configure the base properties
b.ConfigureByConvention();
//Configure other properties (if you are using the fluent API)
b.Property(x => x.Name).IsRequired().HasMaxLength(128);
});
}
````
* 这里调用了 `b.ConfigureByConvention()` 它对于**配置基本属性**非常重要.
* 你可以在这里配置 `Name` 属性或者使用**data annotation attributes**(参阅[EF Core 文档](https://docs.microsoft.com/zh-cn/ef/core/modeling/entity-properties)).
> 尽管有许多扩展方法可以配置基本属性,但如果需要 `ConfigureByConvention()` 内部会调用它们. 因此仅调用它就足够了.
### 配置连接字符串选择 ### 配置连接字符串选择
如果你的应用程序有多个数据库,你可以使用 `connectionStringName]` Attribute为你的DbContext配置连接字符串名称. 如果你的应用程序有多个数据库,你可以使用 `connectionStringName]` Attribute为你的DbContext配置连接字符串名称.
@ -225,7 +272,7 @@ public override async Task DeleteAsync(
} }
```` ````
### 访问 EF Core API ## 访问 EF Core API
大多数情况下应该隐藏仓储后面的EF Core API(这也是仓储的设计目地). 但是如果想要通过仓储访问DbContext实现,则可以使用`GetDbContext()`或`GetDbSet()`扩展方法. 例: 大多数情况下应该隐藏仓储后面的EF Core API(这也是仓储的设计目地). 但是如果想要通过仓储访问DbContext实现,则可以使用`GetDbContext()`或`GetDbSet()`扩展方法. 例:
@ -251,9 +298,60 @@ public class BookService
> 要点: 你必须在使用`DbContext`的项目里引用`Volo.Abp.EntityFrameworkCore`包. 这会破坏封装,但在这种情况下,这就是你需要的. > 要点: 你必须在使用`DbContext`的项目里引用`Volo.Abp.EntityFrameworkCore`包. 这会破坏封装,但在这种情况下,这就是你需要的.
### 高级主题 ## Extra Properties & Object Extension Manager
额外属性系统允许你为实现了 `IHasExtraProperties` 的实体set/get动态属性. 当你想将自定义属性添加到[应用程序模块](Modules/Index.md)中定义的实体时,它特别有用.
默认,实体的所有额外属性存储在数据库的一个 `JSON` 对象中.
#### 设置默认仓储类 实体扩展系统允许你存储额外属性在数据库的单独字段中. 有关额外属性和实体扩展系统的更多信息,请参阅下列文档:
* [自定义应用模块: 扩展实体](Customizing-Application-Modules-Extending-Entities.md)
* [实体](Entities.md)
本节只解释了 EF Core相关的 `ObjectExtensionManager` 及其用法.
### ObjectExtensionManager.Instance
`ObjectExtensionManager` 实现单例模式,因此你需要使用静态的 `ObjectExtensionManager.Instance` 来执行所有操作。
### MapEfCoreProperty
`MapEfCoreProperty` 是一种快捷扩展方法,用于定义实体的扩展属性并映射到数据库.
**示例**: 添加 `Title` 属性 (数据库字段)到 `IdentityRole` 实体:
````csharp
ObjectExtensionManager.Instance
.MapEfCoreProperty<IdentityRole, string>(
"Title",
builder => { builder.HasMaxLength(64); }
);
````
如果相关模块已实现此功能(通过使用下面说明的 `ConfigureEfCoreEntity`)则将新属性添加到模型中. 然后你需要运行标准的 `Add-Migration` 和 `Update-Database` 命令更新数据库以添加新字段.
>`MapEfCoreProperty` 方法必须在使用相关的 `DbContext` 之前调用,它是一个静态方法. 最好的方法是尽早的应用程序中使用它. 应用程序启动模板含有 `YourProjectNameEntityExtensions` 类,可以在放心的在此类中使用此方法.
### ConfigureEfCoreEntity
如果你正在开发一个可重用使用的模块,并允许应用程序开发人员将属性添加到你的实体,你可以在实体映射使用 `ConfigureEfCoreEntity` 扩展方法,但是在配置实体映射时可以使用快捷的扩展方法 `ConfigureObjectExtensions`:
````csharp
builder.Entity<YourEntity>(b =>
{
b.ConfigureObjectExtensions();
//...
});
````
如果你调用 `ConfigureByConvention()` 扩展方法(在此示例中 `b.ConfigureByConvention`),ABP框架内部会调用 `ConfigureObjectExtensions` 方法. 使用 `ConfigureByConvention` 方法是**最佳实践**,因为它还按照约定配置基本属性的数据库映射.
参阅上面提到的 "*ConfigureByConvention 方法*" 了解更多信息.
## 高级主题
### 设置默认仓储类
默认的通用仓储的默认实现是`EfCoreRepository`类,你可以创建自己的实现,并将其做为默认实现 默认的通用仓储的默认实现是`EfCoreRepository`类,你可以创建自己的实现,并将其做为默认实现
@ -343,3 +441,7 @@ context.Services.AddAbpDbContext<OtherDbContext>(options =>
```` ````
在这个例子中,`OtherDbContext`实现了`IBookStoreDbContext`. 此功能允许你在开发时使用多个DbContext(每个模块一个),但在运行时可以使用单个DbContext(实现所有DbContext的所有接口). 在这个例子中,`OtherDbContext`实现了`IBookStoreDbContext`. 此功能允许你在开发时使用多个DbContext(每个模块一个),但在运行时可以使用单个DbContext(实现所有DbContext的所有接口).
## 另请参阅
* [实体](Entities.md)

3
docs/zh-Hans/Getting-Started-Angular-Template.md

@ -0,0 +1,3 @@
## Getting Started With the Angular Application Template
TODO...

6
docs/zh-Hans/Getting-Started-With-Startup-Templates.md

@ -0,0 +1,6 @@
# 启动模板入门
参阅下面的教程来学习如何开始使用的ABP框架预构建的应用程序启动模板:
* [ASP.NET Core MVC/Razor页面模板入门](Getting-Started-AspNetCore-MVC-Template.md)
* [Angular UI模板入门](Getting-Started-Angular-Template.md)

10
docs/zh-Hans/Startup-Templates/Application.md

@ -2,11 +2,16 @@
## 介绍 ## 介绍
应用程序启动模板是基于[领域驱动设计](../Domain-Driven-Design.md)(DDD)分层的应用程序结构. 在这篇文档中详细介绍了解决方案结构和项目,如果你想快速入门,请遵循以下指南: 应用程序启动模板是基于[领域驱动设计](../Domain-Driven-Design.md)(DDD)分层的应用程序结构.
* 参阅[ASP.NET Core MVC 模板入门](../Getting-Started-AspNetCore-MVC-Template.md)创建此模板的新解决方案并运行它. 在这篇文档中详细介绍了**解决方案结构**和项目,如果你想快速入门,请遵循以下指南:
* 参阅[ASP.NET Core MVC 模板入门](../Getting-Started-With-Startup-Templates.md)创建此模板的新解决方案并运行它.
* 参阅[ASP.NET Core MVC 教程](../Tutorials/AspNetCore-Mvc/Part-I.md)学习使用此模板开发应用程序. * 参阅[ASP.NET Core MVC 教程](../Tutorials/AspNetCore-Mvc/Part-I.md)学习使用此模板开发应用程序.
* [入门文档](../Getting-Started-AspNetCore-MVC-Template.md) 介绍了如何在几分钟内创建一个新的应用程序.
* [应用程序开发教程](../Tutorials/AspNetCore-Mvc/Part-I.md) 学习使用此模板开发应用程序.
## 如何开始 ## 如何开始
你可以使用[ABP CLI](../CLI.md)创建基于此启动模板的新项目,或者你也可以在[入门](https://abp.io/get-started)页面创建并下载项目. 在这里我们使用CLI创建新项目. 你可以使用[ABP CLI](../CLI.md)创建基于此启动模板的新项目,或者你也可以在[入门](https://abp.io/get-started)页面创建并下载项目. 在这里我们使用CLI创建新项目.
@ -120,6 +125,7 @@ ABP是一个模块化的框架,理想的设计是让每个模块都有自己的
* 它依赖 `.EntityFrameworkCore` 项目,因为它重用了应用程序的 `DbContext` 配置 . * 它依赖 `.EntityFrameworkCore` 项目,因为它重用了应用程序的 `DbContext` 配置 .
> 只有在你使用了EF Core做为数据库提供程序时,此项目才会可用. > 只有在你使用了EF Core做为数据库提供程序时,此项目才会可用.
> 参阅[Entity Framework Core迁移指南](../Entity-Framework-Core-Migrations.md)了解这个项目的详细信息.
#### .DbMigrator 项目 #### .DbMigrator 项目

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

@ -1,3 +1,469 @@
# ASP.NET Core (MVC / Razor Pages) 用户界面自定义指南 # ASP.NET Core (MVC / Razor Pages) 用户界面自定义指南
TODO... 本文档解释了如何重写ASP.NET Core MVC / Razor Page 应用程序依赖[应用模块](../../Modules/Index.md)的用户界面.
## 重写页面
本节介绍了[Razor 页面](https://docs.microsoft.com/zh-cn/aspnet/core/razor-pages/)开发,它是ASP.NET Core推荐的服务端渲染用户页面的方法. 预构建的模块通常使用Razor页面替代经典的MVC方式(下一节也介绍MVC模式).
你通过有三种重写页面的需求:
* 仅**重写页面模型**(C#)端执行其他逻辑,不更改UI.
* 仅**重写Razor页面**(.cshtml文件),不更改逻辑.
* **完全重写** 页面.
### 重写页面模型 (C#)
````csharp
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity;
using Volo.Abp.Identity.Web.Pages.Identity.Users;
namespace Acme.BookStore.Web.Pages.Identity.Users
{
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(EditModalModel))]
public class MyEditModalModel : EditModalModel
{
public MyEditModalModel(
IIdentityUserAppService identityUserAppService,
IIdentityRoleAppService identityRoleAppService
) : base(
identityUserAppService,
identityRoleAppService)
{
}
public override async Task<IActionResult> OnPostAsync()
{
//TODO: Additional logic
await base.OnPostAsync();
//TODO: Additional logic
}
}
}
````
* 这个类继承并替换 `EditModalModel` ,重写了 `OnPostAsync` 方法在基类代码的前后执行附加逻辑
* 它使用 `ExposeServices` 和 `Dependency` attributes去替换这个类.
### 重写Razor页面 (.CSHTML)
使用[虚拟文件系统](../../Virtual-File-System.md)可以重写 `.cshtml` 文件(razor page, razor view, view component... 等.)
虚拟文件系统允许我们将**资源嵌入到程序集中**. 通过这个方式,预构建的模块在Nuget包中定义了Razor页面. 当你依赖模块时,可以覆盖这个模块向虚拟文件系统添加的任何文件,包括页面/视图.
#### 示例
这个示例重写了[账户模块](../../Modules/Account.md)定义的**登录页面**UI
物理文件可以覆盖相同位置的嵌入文件. 账户模块在 `Pages/Account` 文件夹下定义了 `Login.cshtml` 文件. 所以你可以在同一路径下创建文件覆盖它:
![overriding-login-cshtml](../../images/overriding-login-cshtml.png)
通常你想要拷贝模块的 `.cshtml` 原文件,然后进行需要的更改. 你可以在[这里](https://github.com/abpframework/abp/blob/dev/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml)找到源文件. 不要拷贝 `Login.cshtml.cs` 文件,它是隐藏razor页面的代码,我们不希望覆盖它(见下节).
这就够了,接下来你可以对文件内容做你想要的更改.
### 完全重写Razo页面
也许你想要完全重写页面,Razor和页面相关的C#文件.
在这种情况下;
1. 像上面描述过的那术重写C#页面模型类,但不需要替换已存在的页面模型类.
2. 像上面描述过的那样重写Razor页面,并且更改@model指向新的页面模型
#### 示例
这个示例重写了[账户模块](../../Modules/Account.md)定义的**登录页面**
创建一个继承自 `LoginModel`(定义在`Volo.Abp.Account.Web.Pages.Account`命名空间下)的页面模型类:
````csharp
public class MyLoginModel : LoginModel
{
public MyLoginModel(
IAuthenticationSchemeProvider schemeProvider,
IOptions<AbpAccountOptions> accountOptions
) : base(
schemeProvider,
accountOptions)
{
}
public override Task<IActionResult> OnPostAsync(string action)
{
//TODO: Add logic
return base.OnPostAsync(action);
}
//TODO: Add new methods and properties...
}
````
如果需要,你可以重写任何方法或添加新的属性/方法
> 注意我们没有使用 `[Dependency(ReplaceServices = true)]` 或 `[ExposeServices(typeof(LoginModel))]`,因为我们不想替换依赖注入中已存在的类,我们定义了一个新的.
拷贝 `Login.cshtml` 到你们解决方案,更改 **@model** 指定到 `MyLoginModel`:
````xml
@page
...
@model Acme.BookStore.Web.Pages.Account.MyLoginModel
...
````
这就够了,接下来你可以做任何想要更改.
#### 不使用继承替换页面模型
你不需要继承源页面模型类(像之前的示例). 你可以完全**重写实现**你自己的页面. 在这种事情下你可以从 `PageModel`,`AbpPageModel` 或任何你需要的合适的基类派生.
## 重写视图组件
在ABP框架,预构建的模块和主题定义了一些**可重用的视图组件**. 这些视图组件可以像页面一样被替换.
### 示例
下面是应用程序启动模板自带的 **基本主题** 的截图.
![bookstore-brand-area-highlighted](../../images/bookstore-brand-area-highlighted.png)
[基本主题](../../Themes/Basic.md) 为layout定义了一些视图组件. 例如上面带有红色矩形的突出显示区域称为 **Brand组件**, 你可能想添加自己的**自己的应用程序logo**来自定义此组件. 让我们来看看如何去做.
首先创建你的logo并且放到你的web应用程序文件夹中,我们使用 `wwwroot/logos/bookstore-logo.png` 路径. 然后在 `Themes/Basic/Components/Brand` 文件夹下复制[Brand组件视图](https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Brand/Default.cshtml). 结果应该是类似下面的图片:
![bookstore-added-brand-files](../../images/bookstore-added-brand-files.png)
然后对 `Default.cshtml` 文件做你想要的更改. 例如内容可以是这样的:
````xml
<a href="/">
<img src="~/logos/bookstore-logo.png" width="250" height="60"/>
</a>
````
现在你可以运行应用程序看到结果:
![bookstore-added-logo](../../images/bookstore-added-logo.png)
如果你需要,你也可以仅使用依赖注入系统替换组件[背后的C#类代码](https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Brand/MainNavbarBrandViewComponent.cs)
### 重写主题
正如上所解释的,你可以更改任何组件,layout或c#类. 参阅[主题文档]了解更多关于主题系统的信息.
## 重写静态资源
重写模块的静态资源(像JavaScript,Css或图片文件)是很简单的. 只需要在解决方案的相同路径创建文件,虚拟文件系统会自动处理它.
## 操作捆绑
[捆绑 & 压缩](Bundling-Minification.md) 系统提供了**动态可扩展的** 系统去创建**script**和**style**捆绑. 它允许你扩展和操作现有的包.
### 示例: 添加全局CSS文件
例如APP框架定义了一个**全局样式捆绑**添加到所有的页面(事实上由主题添加layout). 让我们添加一个**自定义样式文件**到这个捆绑文件的最后,我们可以覆盖任何全局样式.
创建在 `wwwroot` 文件夹下创建一个CSS文件
![bookstore-global-css-file](../../images/bookstore-global-css-file.png)
在CSS文件中定义一些规则. 例如:
````css
.card-title {
color: orange;
font-size: 2em;
text-decoration: underline;
}
.btn-primary {
background-color: red;
}
````
然后在你的[模块](../../Module-Development-Basics.md) `ConfigureServices` 方法添加这个文件到标准的全局样式捆绑包:
````csharp
Configure<AbpBundlingOptions>(options =>
{
options.StyleBundles.Configure(
StandardBundles.Styles.Global, //The bundle name!
bundleConfiguration =>
{
bundleConfiguration.AddFiles("/styles/my-global-styles.css");
}
);
});
````
#### 全局脚本捆绑包
就像 `StandardBundles.Styles.Global` 一样,还有一个 `StandardBundles.Scripts.Global`,你可以添加文件或操作现有文件.
### 示例: 操作捆绑包文件
上面的示例中添加了新文件到捆绑包. 如果你创建 **bundle contributor** 类则可以做到更多. 示例:
````csharp
public class MyGlobalStyleBundleContributor : BundleContributor
{
public override void ConfigureBundle(BundleConfigurationContext context)
{
context.Files.Clear();
context.Files.Add("/styles/my-global-styles.css");
}
}
````
然后你可以添加这个contributor到已存在的捆绑中:
````csharp
Configure<AbpBundlingOptions>(options =>
{
options.StyleBundles.Configure(
StandardBundles.Styles.Global,
bundleConfiguration =>
{
bundleConfiguration.AddContributors(typeof(MyGlobalStyleBundleContributor));
}
);
});
````
示例中清除了所有的CSS文件,在现实中这并不是一个好主意,你可以找到某个特定的文件替换成你自己的文件.
### 示例: 为特定页面添加JavaScript文件
上面的示例将全局包添加到布局中. 如果要在依赖模块中为特定页面定义添加CSS/JavaScript文件(或替换文件)怎么做?
假设你想要用户进入身份模块的**角色管理**页面时运行**JavaScript代码**.
首先在 `wwwroot`, `Pages` 或 `Views` 文件夹下创建一个标准的JavaScript文件(默认ABP支持这些文件夹下的静态文件). 根据约定我们推荐 `Pages/Identity/Roles` 文件夹:
![bookstore-added-role-js-file](../../images/bookstore-added-role-js-file.png)
该文件的内容很简单:
````js
$(function() {
abp.log.info('My custom role script file has been loaded!');
});
````
然后将这个文件添加到角色管页面理捆绑包中:
````csharp
Configure<AbpBundlingOptions>(options =>
{
options.ScriptBundles
.Configure(
typeof(Volo.Abp.Identity.Web.Pages.Identity.Roles.IndexModel).FullName,
bundleConfig =>
{
bundleConfig.AddFiles("/Pages/Identity/Roles/my-role-script.js");
});
});
````
`typeof(Volo.Abp.Identity.Web.Pages.Identity.Roles.IndexModel).FullName` 是获取角色管理页面捆绑包名称的安全方式:
> 请注意并非每个页面都定义了这个页面的捆绑包. 它们仅在需要时定义.
除了添加新的CSS/JavaScript文件到页面,你也可以以替换(通过捆绑包contributor)已存在.
## 布局定制
布局由主题([参阅主题](Theming.md))定义设计. 它们不包含在下载的应用程序解决方案中. 通过这种方式你可以轻松的**更改**主题并获取新的功能. 你不能**直接更改**应用程序中的布局代码,除非你用自己的布局替换它(在下一部分中说明).
有一些通用的方法可以**自定义布局**,将在下一节中介绍.
### 菜单贡献者
ABP框架定义了两个**标准菜单**:
![bookstore-menus-highlighted](../../images/bookstore-menus-highlighted.png)
* `StandardMenus.Main`: 应用程序的主菜单.
* `StandardMenus.User`: 用户菜单 (通常在屏幕的右上方).
显示菜单是主题的责任,但**菜单项**由模板和你的应用程序代码决定. 只需要实现 `IMenuContributor` 接口并在 `ConfigureMenuAsync` 方法操作菜单项.
渲染菜单时需要执行菜单贡献者. **应用程序启动模板** 已经定义了菜单贡献者,所以你可以使用它. 参阅[导航菜单](Navigation-Menu.md)文档了解更多.
### 工具栏贡献者
[工具栏系统](Toolbars.md)用于在用户界面定义 **工具栏** . 模块 (或你的应用程序)可以将 **项** 添加到工具栏, 随后主题将在**布局**上呈现工具栏.
只有一个 **标准工具栏** (名称为 "Main" - 定义为常量: `StandardToolbars.Main`). 对于基本主题,按如下呈现:![bookstore-toolbar-highlighted](../../images/bookstore-toolbar-highlighted.png)
在上面的屏幕快照中,主工具栏添加了两个项目:语言开关组件和用户菜单. 你可以在此处添加自己的项.
#### 示例: 添加通知图标
在这个示例中,我们会添加一个**通知(响铃)图标**到语言切换项的左侧. 工具栏的项项目是一个**视图组件**. 所以,在你的项目中创建一个新的视图组件:
![bookstore-notification-view-component](../../images/bookstore-notification-view-component.png)
**NotificationViewComponent.cs**
````csharp
public class NotificationViewComponent : AbpViewComponent
{
public async Task<IViewComponentResult> InvokeAsync()
{
return View("/Pages/Shared/Components/Notification/Default.cshtml");
}
}
````
**Default.cshtml**
````xml
<div id="MainNotificationIcon" style="color: white; margin: 8px;">
<i class="far fa-bell"></i>
</div>
````
现在,我们创建一个类实现 `IToolbarContributor` 接口:
````csharp
public class MyToolbarContributor : IToolbarContributor
{
public Task ConfigureToolbarAsync(IToolbarConfigurationContext context)
{
if (context.Toolbar.Name == StandardToolbars.Main)
{
context.Toolbar.Items
.Insert(0, new ToolbarItem(typeof(NotificationViewComponent)));
}
return Task.CompletedTask;
}
}
````
这个类向 `Main` 工具栏的第一项添加了 `NotificationViewComponent`.
最后你需要将这个贡献者添加到 `AbpToolbarOptions`,在你模块类的 `ConfigureServices` 方法:
````csharp
Configure<AbpToolbarOptions>(options =>
{
options.Contributors.Add(new MyToolbarContributor());
});
````
这就够了,当你运行应用程序后会看到工具栏上的通知图标:
![bookstore-notification-icon-on-toolbar](../../images/bookstore-notification-icon-on-toolbar.png)
示例中的 `NotificationViewComponent` 返回没有任何数据的视图. 在实际场景中,你可能想**查询数据库**(或调用HTTP API)获取通知并传递给视图. 如果需要可以将 `JavaScript` 或 `CSS` 文件添加到工具栏的全局捆绑包中(如前所述).
参阅[工具栏文档](Toolbars.md)了解更多关于工具栏系统.
### 布局钩子
[布局钩子](Layout-Hooks.md) 系统允许你在布局页面的某些特定部分 **添加代码** . 所有主题的所有布局都应该实现这些钩子. 然后你可以将**视图组件**添加到钩子.
#### 示例: 添加谷歌统计
假设你想要添加谷歌统计脚本到布局(将适用所有的页面). 首先在你的项目中**创建一个视图组件**:
![bookstore-google-analytics-view-component](../../images/bookstore-google-analytics-view-component.png)
**NotificationViewComponent.cs**
````csharp
public class GoogleAnalyticsViewComponent : AbpViewComponent
{
public IViewComponentResult Invoke()
{
return View("/Pages/Shared/Components/GoogleAnalytics/Default.cshtml");
}
}
````
**Default.cshtml**
````html
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-xxxxxx-1', 'auto');
ga('send', 'pageview');
</script>
````
在你自己的代码中更改 `UA-xxxxxx-1` .
然后你可以在你模块的 `ConfigureServices` 方法将这个组件添加到任何的钩子点:
````csharp
Configure<AbpLayoutHookOptions>(options =>
{
options.Add(
LayoutHooks.Head.Last, //The hook name
typeof(GoogleAnalyticsViewComponent) //The component to add
);
});
````
现在谷歌统计代码将在页面的 `head` 所为最后一项插入. 你(或你在使用的模块)可以将多个项添加到相同的钩子,它们都会添加到布局.
在上面我们添加 `GoogleAnalyticsViewComponent` 到所有的布局,你可能只想添加到指定的布局:
````csharp
Configure<AbpLayoutHookOptions>(options =>
{
options.Add(
LayoutHooks.Head.Last,
typeof(GoogleAnalyticsViewComponent),
layout: StandardLayouts.Application //Set the layout to add
);
});
````
参阅下面的布局部分,以了解有关布局系统的更多信息.
### 布局
布局系统允许主题定义标准,命名布局并且允许任何页面选择使用合适的布局. 有三种预定义的布局:
* "**Application**": 应用程序的主要(和默认)布局. 它通常包含页眉,菜单(侧栏),页脚,工具栏等.
* "**Account**": 登录,注册和其他类似页面使用此布局. 默认它用于 `/Pages/Account` 文件夹下的页面.
* "**Empty**": 空的最小的布局.
这些名称在 `StandardLayouts` 类定义为常量. 这是标准的布局名称,所有的主题开箱即用的实现. 你也可以创建自己的布局.
#### 布局位置
你可以在[这里](https://github.com/abpframework/abp/tree/dev/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts)找到基本主题的布局文件. 你可以将它们作用构建自己的布局的参考,也可以在必要时覆盖它们.
#### ITheme
ABP框架使用 `ITheme` 服务通过局部名称获取布局位置. 你可以替换此服务动态的选择布局位置.
#### IThemeManager
`IThemeManager` 用于获取当前主题,并得到了布局路径. 任何页面可以都决定自己的布局. 例:
````html
@using Volo.Abp.AspNetCore.Mvc.UI.Theming
@inject IThemeManager ThemeManager
@{
Layout = ThemeManager.CurrentTheme.GetLayout(StandardLayouts.Empty);
}
````
此页面将使用空白布局. 它使用 `ThemeManager.CurrentTheme.GetEmptyLayout()` 扩展方法.
如果你设置特定目录下所有页面的布局,可以在该文件夹下的 `_ViewStart.cshtml` 文件编写以上代码.

80
docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Alerts.md

@ -0,0 +1,80 @@
# Alerts
## 介绍
`abp-alert` 是创建alert的主要元素.
基本用法:
````xml
<abp-alert alert-type="Primary">
A simple primary alert—check it out!
</abp-alert>
````
## Demo
参阅[alerts demo页面](https://bootstrap-taghelpers.abp.io/Components/Alerts)查看示例.
## Attributes
### alert-type
值做为alert的Type,应为以下值之一:
* `Default` (默认值)
* `Primary`
* `Secondary`
* `Success`
* `Danger`
* `Warning`
* `Info`
* `Light`
* `Dark`
示例:
````xml
<abp-alert alert-type="Warning">
A simple warning alert—check it out!
</abp-alert>
````
### alert-link
alert的链接.
示例:
````xml
<abp-alert alert-type="Danger">
A simple danger alert with <a abp-alert-link href="#">an example link</a>. Give it a click if you like.
</abp-alert>
````
### dismissible
使alert可被忽略:
示例:
````xml
<abp-alert alert-type="Warning" dismissible="true">
Holy guacamole! You should check in on some of those fields below.
</abp-alert>
````
### Additional content
`abp-alert`还可以包含其他HTML元素,例如标题,段落和分隔符.
示例:
````xml
<abp-alert alert-type="Success">
<h4>Well done!</h4>
<p>Aww yeah, you successfully read this important alert message. This example text is going to run a bit longer so that you can see how spacing within an alert works with this kind of content.</p>
<hr>
<p class="mb-0">Whenever you need to, be sure to use margin utilities to keep things nice and tidy.</p>
</abp-alert>
````

4
docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Buttons.md

@ -12,7 +12,7 @@
## Demo ## Demo
参阅 [按钮Demo页面](https://bootstrap-taghelpers.abp.io/Components/Buttons) 查看示例. 参阅[按钮Demo页面](https://bootstrap-taghelpers.abp.io/Components/Buttons)查看示例.
## Attributes ## Attributes
@ -20,7 +20,7 @@
指定按钮的主样式/类型. 应为以下值之一: 指定按钮的主样式/类型. 应为以下值之一:
* `Default` (default value) * `Default` (默认值)
* `Primary` * `Primary`
* `Secondary` * `Secondary`
* `Success` * `Success`

180
docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Cards.md

@ -0,0 +1,180 @@
# 卡片
## 介绍
`abp-card` 是从bootstrap card元素派生的内容容器.
基本用法:
````xml
<abp-card style="width: 18rem;">
<img abp-card-image="Top" src="~/imgs/demo/300x200.png"/>
<abp-card-body>
<abp-card-title>Card Title</abp-card-title>
<abp-card-text>Some quick example text to build on the card title and make up the bulk of the card's content.</abp-card-text>
<a abp-button="Primary" href="#"> Go somewhere</a>
</abp-card-body>
</abp-card>
````
##### 使用标题,文本和链接:
`abp-card` 可以使用以下标签
* `abp-card-title`
* `abp-card-subtitle`
* `a abp-card-link`
示例:
````xml
<abp-card style="width: 18rem;">
<abp-card-body>
<abp-card-title>Card title</abp-card-title>
<abp-card-subtitle class="mb-2 text-muted">Card subtitle</abp-card-subtitle>
<abp-card-text>Some quick example text to build on the card title and make up the bulk of the card's content.</abp-card-text>
<a abp-card-link href="#">Card link</a>
<a abp-card-link href="#">Another link</a>
</abp-card-body>
</abp-card>
````
##### 使用列表组:
* `abp-list-group flush="true"` : `flush` 属性渲染到 bootstrap `list-group-flush` 的 calss 中,该类用于删除边界和圆角以使列表组项在父容器中并排显示.
* `abp-list-group-item`
示例:
````xml
<abp-card style="width: 18rem;">
<img abp-card-image="Top" src="~/imgs/demo/300x200.png" />
<abp-card-body>
<abp-card-title>Card Title</abp-card-title>
<abp-card-text>Some quick example text to build on the card title and make up the bulk of the card's content.</abp-card-text>
</abp-card-body>
<abp-list-group flush="true">
<abp-list-group-item>Cras justo odio</abp-list-group-item>
<abp-list-group-item>Dapibus ac facilisis in</abp-list-group-item>
<abp-list-group-item>Vestibulum at eros</abp-list-group-item>
</abp-list-group>
<abp-card-body>
<a abp-card-link href="#">Card link</a>
<a abp-card-link href="#">Another link</a>
</abp-card-body>
</abp-card>
````
##### 使用页眉,页脚和块引用:
* `abp-card-header`
* `abp-card-footer`
* `abp-blockquote`
示例:
```xml
<abp-card style="width: 18rem;">
<abp-card-header>Featured</abp-card-header>
<abp-card-body>
<abp-card-title> Special title treatment</abp-card-title>
<abp-card-text>With supporting text below as a natural lead-in to additional content.</abp-card-text>
<a abp-button="Primary" href="#"> Go somewhere</a>
</abp-card-body>
</abp-card>
```
引用示例:
```xml
<abp-card>
<abp-card-header>Quote</abp-card-header>
<abp-card-body>
<abp-blockquote>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.</p>
<footer>Someone famous in Source Title</footer>
</abp-blockquote>
</abp-card-body>
</abp-card>
```
页脚示例:
```xml
<abp-card class="text-center">
<abp-card-header>Featured</abp-card-header>
<abp-card-body>
<abp-blockquote>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.</p>
<footer>Someone famous in Source Title</footer>
</abp-blockquote>
</abp-card-body>
<abp-card-footer class="text-muted"> 2 days ago</abp-card-footer>
</abp-card>
```
## Demo
参阅[卡片demo页面](https://bootstrap-taghelpers.abp.io/Components/Cards)查看示例.
## abp-card Attributes
- **background:** 值指定卡片背景的颜色.
- **text-color**: 值指定卡片内文本的颜色.
- **border:** 值指定卡片边框的颜色.
应为以下值之一:
* `Default` (默认值)
* `Primary`
* `Secondary`
* `Success`
* `Danger`
* `Warning`
* `Info`
* `Light`
* `Dark`
示例:
````xml
<abp-card background="Success" text-color="Danger" border="Dark">
````
### sizing
卡片的默认值为100%,可以使用自定义CSS,栅格类,栅格Sass mixins或[utilities](https://getbootstrap.com/docs/4.0/utilities/sizing/)进行更改.
````xml
<abp-card style="width: 18rem;">
````
### card-deck 和 card-columns
`abp-card` 可以在 `card-deck` 或 `card-columns` 里使用.
````xml
<div class="card-deck">
<abp-card background="Primary">
<abp-card-header>First Deck</abp-card-header>
<abp-card-body>
<abp-card-title> Ace </abp-card-title>
<abp-card-text>Here is the content for Ace.</abp-card-text>
</abp-card-body>
</abp-card>
<abp-card background="Info">
<abp-card-header>Second Deck</abp-card-header>
<abp-card-body>
<abp-card-title> Beta </abp-card-title>
<abp-card-text>Beta content.</abp-card-text>
</abp-card-body>
</abp-card>
<abp-card background="Warning">
<abp-card-header>Third Deck</abp-card-header>
<abp-card-body>
<abp-card-title> Epsilon </abp-card-title>
<abp-card-text>Content for Epsilon.</abp-card-text>
</abp-card-body>
</abp-card>
</div>
````

90
docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Collapse.md

@ -0,0 +1,90 @@
# Collapse
## 介绍
`abp-collapse-body` 是显示和隐藏内容的主要容器. `abp-collapse-id` 用于显示和隐藏内容容器. 可以通过 `abp-button` 和 `a` 标签触发.
基本用法:
````xml
<abp-button button-type="Primary" abp-collapse-id="collapseExample" text="Button with data-target" />
<a abp-button="Primary" abp-collapse-id="collapseExample"> Link with href </a>
<abp-collapse-body id="collapseExample">
Anim pariatur wolf moon tempor,,, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.
</abp-collapse-body>
````
## Demo
参阅[collapse demo 页面](https://bootstrap-taghelpers.abp.io/Components/Collapse)查看示例.
## Attributes
### show
指定折叠主体初始化时是折叠还是展开. 应为以下值之一:
* `false` (默认值)
* `true`
### multi
指定 `abp-collapse-body` 是否可以通过显示/隐藏多个折叠体的元素显示或隐藏. 此属性做为 "multi-collapse" class 添加到 `abp-collapse-body`. 应为以下值之一:
* `false` (默认值)
* `true`
示例:
````xml
<a abp-button="Primary" abp-collapse-id="FirstCollapseExample"> Toggle first element </a>
<abp-button button-type="Primary" abp-collapse-id="SecondCollapseExample" text="Toggle second element" />
<abp-button button-type="Primary" abp-collapse-id="FirstCollapseExample SecondCollapseExample" text="Toggle both elements" />
<abp-row class="mt-3">
<abp-column size-sm="_6">
<abp-collapse-body id="FirstCollapseExample" multi="true">
Curabitur porta porttitor libero eu luctus. Praesent ultrices mattis commodo. Integer sodales massa risus, in molestie enim sagittis blandit
</abp-collapse-body>
</abp-column>
<abp-column size-sm="_6">
<abp-collapse-body id="SecondCollapseExample" multi="true">
Anim pariatur wolf moon tempor,,, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et.
</abp-collapse-body>
</abp-column>
</abp-row>
````
## 手风琴示例
`abp-accordion` 是手风琴项的主容器.
基本用法:
````xml
<abp-accordion>
<abp-accordion-item title="Collapsible Group Item #1">
Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry rtat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.
</abp-accordion-item>
<abp-accordion-item title="Collapsible Group Item #2">
Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.
</abp-accordion-item>
<abp-accordion-item title="Collapsible Group Item #3">
Anim pariatur wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.
</abp-accordion-item>
</abp-accordion>
````
## Attributes
### active
指定手风琴项目在初始化时是显示还是隐藏. 应为以下值之一:
* `false` (默认值)
* `true`
### title
指定手风琴项的标题. 应为字符串类型的值.

93
docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Dropdowns.md

@ -0,0 +1,93 @@
# 下拉菜单
## 介绍
`abp-dropdown` 是下拉菜单的主要容器.
基本用法:
````xml
<abp-dropdown>
<abp-dropdown-button text="Dropdown button" />
<abp-dropdown-menu>
<abp-dropdown-item href="#">Action</abp-dropdown-item>
<abp-dropdown-item href="#">Another action</abp-dropdown-item>
<abp-dropdown-item href="#">Something else here</abp-dropdown-item>
</abp-dropdown-menu>
</abp-dropdown>
````
## Demo
参阅 [下拉菜单demo页面](https://bootstrap-taghelpers.abp.io/Components/Dropdowns)查看示例.
## Attributes
### direction
指定下拉菜单的方向. 应为以下值之一:
* `Down` (默认值)
* `Up`
* `Right`
* `Left`
### dropdown-style
指定 `abp-dropdown-button` 是否具有用于拆分的拆分图标. 应为以下值之一:
* `Single` (默认值)
* `Split`
## Menu items
`abp-dropdown-menu` 下拉菜单项的主要容器.
基本用法:
````xml
<abp-dropdown>
<abp-dropdown-button button-type="Secondary" text="Dropdown"/>
<abp-dropdown-menu>
<abp-dropdown-header>Dropdown Header</abp-dropdown-header>
<abp-dropdown-item href="#">Action</abp-dropdown-item>
<abp-dropdown-item active="true" href="#">Active action</abp-dropdown-item>
<abp-dropdown-item disabled="true" href="#">Disabled action</abp-dropdown-item>
<abp-dropdown-divider/>
<abp-dropdown-item-text>Dropdown Item Text</abp-dropdown-item-text>
<abp-dropdown-item href="#">Something else here</abp-dropdown-item>
</abp-dropdown-menu>
</abp-dropdown>
````
## Attributes
### align
指定 `abp-dropdown-menu` 在哪个方向对齐. 应为以下值之一:
* `Left` (默认值)
* `Right`
### Additional content
`abp-dropdown-menu` 也可以包含其他HTML元素,例如标题,段落,分隔符或form元素.
示例:
````xml
<abp-dropdown >
<abp-dropdown-button button-type="Secondary" text="Dropdown With Form"/>
<abp-dropdown-menu>
<form class="px-4 py-3">
<abp-input asp-for="EmailAddress"></abp-input>
<abp-input asp-for="Password"></abp-input>
<abp-input asp-for="RememberMe"></abp-input>
<abp-button button-type="Primary" text="Sign In" type="submit" />
</form>
<abp-dropdown-divider></abp-dropdown-divider>
<abp-dropdown-item href="#">New around here? Sign up</abp-dropdown-item>
<abp-dropdown-item href="#">Forgot password?</abp-dropdown-item>
</abp-dropdown-menu>
</abp-dropdown>
````

273
docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Grids.md

@ -0,0 +1,273 @@
# 栅格
## 介绍
ABP标签助手基于bootstrap栅格系统.
## Demo
参阅[网络demo页面](https://bootstrap-taghelpers.abp.io/Components/Grids)查看示例.
### Sizing
**等宽:** 创建等宽的列.
示例:
````xml
<abp-container>
<abp-row>
<abp-column abp-border="Info">1 of 2</abp-column>
<abp-column abp-border="Danger">2 of 2</abp-column>
</abp-row>
<abp-row>
<abp-column abp-border="Primary">1 of 3</abp-column>
<abp-column abp-border="Secondary">2 of 3</abp-column>
<abp-column abp-border="Dark">3 of 3</abp-column>
</abp-row>
</abp-container>
````
**列中断:** `abp-column-breaker` 用于中断当前行的自动放置宽度,然后在新行中开始.
示例:
````xml
<abp-container>
<abp-row>
<abp-column>column</abp-column>
<abp-column>column</abp-column>
<abp-column-breaker/>
<abp-column>column</abp-column>
<abp-column>column</abp-column>
</abp-row>
</abp-container>
````
**设置列宽度:** size属性用于设置特定列的宽度.
示例:
```xml
<abp-container>
<abp-row>
<abp-column>1 of 3</abp-column>
<abp-column size="_6">2 of 3 (wider)</abp-column>
<abp-column>3 of 3</abp-column>
</abp-row>
<abp-row>
<abp-column>1 of 3</abp-column>
<abp-column size="_5">2 of 3 (wider)</abp-column>
<abp-column>3 of 3</abp-column>
</abp-row>
</abp-container>
```
**可变宽度内容:** 根据内容自动调整列的大小.
```xml
<abp-container>
<abp-row h-align="Center">
<abp-column size-lg="_2" abp-border="Info">1 of 3</abp-column>
<abp-column size-md="Auto" abp-border="Danger">Contrary to popular belief, Lorem Ipsum is not simply random text.</abp-column>
<abp-column size-lg="_2" abp-border="Warning">3 of 3</abp-column>
</abp-row>
<abp-row>
<abp-column>1 of 3</abp-column>
<abp-column size-md="Auto">Variable width content</abp-column>
<abp-column size-lg="_2">3 of 3</abp-column>
</abp-row>
</abp-container>
```
### 响应类
响应式类可以在abp标签中强类型使用.
```xml
<abp-row>
<abp-column size-sm="_8">col-sm-8</abp-column>
<abp-column size-sm="_4">col-sm-4</abp-column>
</abp-row>
<abp-row>
<abp-column size-sm="_">col-sm</abp-column>
<abp-column size-sm="_">col-sm</abp-column>
<abp-column size-sm="_">col-sm</abp-column>
<abp-column size-sm="_">col-sm</abp-column>
</abp-row>
<!-- Stack the columns on mobile by making one full-width and the other half-width -->
<abp-row>
<abp-column size="_12" size-md="_8">.col-12 .col-md-8</abp-column>
<abp-column size="_6" size-md="_4">.col-6 .col-md-4</abp-column>
</abp-row>
<!-- Columns start at 50% wide on mobile and bump up to 33.3% wide on desktop -->
<abp-row>
<abp-column size="_6" size-md="_4">.col-6 .col-md-4</abp-column>
<abp-column size="_6" size-md="_4">.col-6 .col-md-4</abp-column>
<abp-column size="_6" size-md="_4">.col-6 .col-md-4</abp-column>
</abp-row>
<!-- Columns are always 50% wide, on mobile and desktop -->
<abp-row>
<abp-column size="_6">.col-6</abp-column>
<abp-column size="_6">.col-6</abp-column>
</abp-row>
```
### 对齐
可以使用强类型的abp标签在垂直和水平方向上进行列对齐.
**垂直对齐**: `v-align` 属性值用于对垂直齐列.
示例:
```xml
<abp-container>
<abp-row v-align="Start">
<abp-column>column</abp-column>
<abp-column>column</abp-column>
<abp-column>column</abp-column>
</abp-row>
<abp-row v-align="Center">
<abp-column>column</abp-column>
<abp-column>column</abp-column>
<abp-column>column</abp-column>
</abp-row>
<abp-row v-align="End">
<abp-column>column</abp-column>
<abp-column>column</abp-column>
<abp-column>column</abp-column>
</abp-row>
</abp-container>
```
**水平对齐**: `h-align` 属性值用于对水平齐列.
示例:
```xml
<abp-container>
<abp-row h-align="Start">
<abp-column size="_4">One of two columns</abp-column>
<abp-column size="_4">One of two columns</abp-column>
</abp-row>
<abp-row h-align="Center">
<abp-column size="_4">One of two columns</abp-column>
<abp-column size="_4">One of two columns</abp-column>
</abp-row>
<abp-row h-align="End">
<abp-column size="_4">One of two columns</abp-column>
<abp-column size="_4">One of two columns</abp-column>
</abp-row>
<abp-row h-align="Around">
<abp-column size="_4">One of two columns</abp-column>
<abp-column size="_4">One of two columns</abp-column>
</abp-row>
<abp-row h-align="Between">
<abp-column size="_4">One of two columns</abp-column>
<abp-column size="_4">One of two columns</abp-column>
</abp-row>
</abp-container>
```
**无间隙**: 可以使用 `gutters="false"` 删除预定义栅格类中列之间的栅格线. 这会从 `abp-row` 中消除负边距,并从所有直接子列中消除水平边距.
示例:
```xml
<abp-row gutters="false">
<abp-column size="_8">One of two columns</abp-column>
<abp-column size="_4">One of two columns</abp-column>
</abp-row>
```
**列包装**: 如果在一行中放置超过12列,则将每组额外的列作为一个单元包装到新行上.
示例:
```xml
<abp-row>
<abp-column size="_9">.col-9</abp-column>
<abp-column size="_4">.col-4<br>Since 9 + 4 = 13 &gt; 12, this 4-column-wide div gets wrapped onto a new line as one contiguous unit.</abp-column>
<abp-column size="_6">.col-6<br>Subsequent columns continue along the new line.s</abp-column>
</abp-row>
```
### 重新排序
**Order类**: `order` 属性用于控制内容的视觉顺序.
示例:
```xml
<abp-container>
<abp-row>
<abp-column order="_12">First, but Last</abp-column>
<abp-column>Second, but unordered</abp-column>
<abp-column order="_6">Third, but Second</abp-column>
</abp-row>
</abp-container>
```
**偏移列**: `offset` 属性用于设置栅格列的偏移量.
示例:
```xml
<abp-container>
<abp-row>
<abp-column size-md="_4">.col-md-4</abp-column>
<abp-column size-md="_4" offset-md="_4">.col-md-4 .offset-md-4</abp-column>
</abp-row>
<abp-row>
<abp-column size-md="_3" offset-md="_3">.col-md-3 .offset-md-3</abp-column>
<abp-column size-md="_3" offset-md="_3">.col-md-3 .offset-md-3</abp-column>
</abp-row>
<abp-row>
<abp-column size-md="_6" offset-md="_3">.col-md-6 .offset-md-3</abp-column>
</abp-row>
<abp-row>
<abp-column size-sm="_5" size-md="_6">.col-sm-5 .col-md-6</abp-column>
<abp-column size-sm="_5" offset-sm="_2" size-md="_6" offset-md="_">.col-sm-5 .offset-sm-2 .col-md-6 .offset-md-0</abp-column>
</abp-row>
<abp-row>
<abp-column size-sm="_6" size-md="_5" size-lg="_6">col-sm-6 .col-md-5 .col-lg-6</abp-column>
<abp-column size-sm="_6" size-md="_5" offset-md="_2" size-lg="_6" offset-lg="_">.col-sm-6 .col-md-5 .offset-md-2 .col-lg-6 .offset-lg-0</abp-column>
</abp-row>
</abp-container>
```
## abp-row Attributes
- **v-align:** 指定包含列的垂直位置. 应为以下值之一:
* `Default` (默认值)
* `Start`
* `Center`
* `End`
- **h-align**: 指定包含列的水平位置. 应为以下值之一:
* `Default` (默认值)
* `Start`
* `Center`
* `Around`
* `Between`
* `End`
- **gutter**: 指定是否将从所有子列中删除负边距和水平填充. 如果未设置,默认为 `true`. 应为以下值之一:
* `true`
* `false`
## abp-column Attributes
- **size:** 指定列的宽度: `_`, `Undefined`, `_1`..`_12`, `Auto`. 或者可以与预定义值一起使用,例如:
- `size-sm`
- `size-md`
- `size-lg`
- `size-xl`
- **order**: 指定列的顺序: `Undefined`, `_1`..`_12`, `First` 和 `Last`.
- **offset:** 指定列的偏移量: `_`, `Undefined`, `_1`..`_12`, `Auto`. 或者可以与预定义值一起使用,例如:
- `offset-sm`
- `offset-md`
- `offset-lg`
- `offset-xl`

12
docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Index.md

@ -13,6 +13,18 @@ ABP框架还向标准bootstrap组件添加了一些**实用的功能**.
这里是ABP框架包装的组件列表: 这里是ABP框架包装的组件列表:
* [Buttons](Buttons.md) * [Buttons](Buttons.md)
* [Cards](Cards.md)
* [Alerts](Alerts.md)
* [Tabs](Tabs.md)
* [Grids](Grids.md)
* [Modals](Modals.md)
* [Collapse](Collapse.md)
* [Dropdowns](Dropdowns.md)
* [List Groups](List-Groups.md)
* [Paginator](Paginator.md)
* [Popovers](Popovers.md)
* [Progress Bars](Progress-Bars.md)
* [Tooltips](Tooltips.md)
* ... * ...
> 在为所有的标签助手完成文档之前,你可以访问 https://bootstrap-taghelpers.abp.io/ 查看在线示例. > 在为所有的标签助手完成文档之前,你可以访问 https://bootstrap-taghelpers.abp.io/ 查看在线示例.

76
docs/zh-Hans/UI/AspNetCore/Tag-Helpers/List-Groups.md

@ -0,0 +1,76 @@
# 列表组
## 介绍
`abp-list-group` 是列表组内容的主要容器.
基本用法:
````xml
<abp-list-group>
<abp-list-group-item>Cras justo odio</abp-list-group-item>
<abp-list-group-item>Dapibus ac facilisis in</abp-list-group-item>
<abp-list-group-item>Morbi leo risus</abp-list-group-item>
<abp-list-group-item>Vestibulum at eros</abp-list-group-item>
</abp-list-group>
````
## Demo
参阅[列表组demo页面](https://bootstrap-taghelpers.abp.io/Components/ListGroups)查看示例.
## Attributes
### flush
指定 `abp-list-group` 项可删除某些边框和圆角,以在父容器中无边框呈现列表组项. 应为以下值之一:
* `false` (默认值)
* `true`
### active
指定 `abp-list-group-item` 是否处于active. 应为以下值之一:
* `false` (默认值)
* `true`
### disabled
指定 `abp-list-group-item` 是否被禁用. 应为以下值之一:
* `false` (默认值)
* `true`
### href
指定 `abp-list-group-item` 是否含有链接. 应该是一个字符串链接值.
### type
指定 `abp-list-group-item` 具有状态背景和颜色的样式类. 应为以下值之一:
* `Default` (默认值)
* `Primary`
* `Secondary`
* `Success`
* `Danger`
* `Warning`
* `Info`
* `Light`
* `Dark`
* `Link`
### Additional content
`abp-list-group-item` 还可以包含其他HTML元素(例如span).
示例:
````xml
<abp-list-group>
<abp-list-group-item>Cras justo odio <span abp-badge-pill="Primary">14</span></abp-list-group-item>
<abp-list-group-item>Dapibus ac facilisis in <span abp-badge-pill="Primary">2</span></abp-list-group-item>
<abp-list-group-item>Morbi leo risus <span abp-badge-pill="Primary">1</span></abp-list-group-item>
</abp-list-group>
````

79
docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Modals.md

@ -0,0 +1,79 @@
# 模态框
## 介绍
`abp-modal` 是创建模态框的主要元素.
基本用法:
````xml
<abp-button button-type="Primary" data-toggle="modal" data-target="#myModal">Launch modal</abp-button>
<abp-modal centered="true" size="Large" id="myModal">
<abp-modal-header title="Modal title"></abp-modal-header>
<abp-modal-body>
Woohoo, you're reading this text in a modal!
</abp-modal-body>
<abp-modal-footer buttons="Close"></abp-modal-footer>
</abp-modal>
````
## Demo
参阅[模态框demo页面](https://bootstrap-taghelpers.abp.io/Components/Modals)查看示例.
## Attributes
### centered
指定模态框的位置. 应为以下值之一:
* `false` (默认值)
* `true`
### size
指定模态框的大小. 应为以下值之一:
* `Default` (默认值)
* `Small`
* `Large`
* `ExtraLarge`
### static
指定模态框是否是静态的. 应为以下值之一:
* `false` (默认值)
* `true`
### Additional content
`abp-modal-footer` 可以有多个带有对齐选项的按钮.
添加 `@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal` 到你的页面.
示例:
````xml
<abp-button button-type="Primary" data-toggle="modal" data-target="#myModal">Launch modal</abp-button>
<abp-modal centered="true" size="Large" id="myModal" static="true">
<abp-modal-header title="Modal title"></abp-modal-header>
<abp-modal-body>
Woohoo, you're reading this text in a modal!
</abp-modal-body>
<abp-modal-footer buttons="@(AbpModalButtons.Save|AbpModalButtons.Close)" button-alignment="Between"></abp-modal-footer>
</abp-modal>
````
### button-alignment
指定模态页脚按钮的位置. 应为以下值之一:
* `Default` (默认值)
* `Start`
* `Center`
* `Around`
* `Between`
* `End`

55
docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Paginator.md

@ -0,0 +1,55 @@
# 分页器
## 介绍
`abp-paginator` 是分页器的abp标签. 需要 `Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination.PagerModel` 类型的模型.
基本用法:
````xml
<abp-paginator model="Model.PagerModel" show-info="true"></abp-paginator>
````
模型:
````csharp
using Microsoft.AspNetCore.Mvc.RazorPages;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components
{
public class PaginatorModel : PageModel
{
public PagerModel PagerModel { get; set; }
public void OnGet(int currentPage, string sort)
{
PagerModel = new PagerModel(100, 10, currentPage, 10, "Paginator", sort);
}
}
}
````
## Demo
参阅[分页器demo页面](https://bootstrap-taghelpers.abp.io/Components/Paginator)查看示例.
## Attributes
### model
`Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination.PagerModel` 类型模型可以用以下数据初始化:
* `totalCount`
* `shownItemsCount`
* `currentPage`
* `pageSize`
* `pageUrl`
* `sort` (默认值为null)
### show-info
指定是否显示开始,结束和总记录的其他信息. 应为以下值之一:
* `false` (默认值)
* `true`

68
docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Progress-Bars.md

@ -0,0 +1,68 @@
# 进度条
## 介绍
`abp-progress-bar` 是进度条状态的abp标签.
基本用法:
````xml
<abp-progress-bar value="70" />
<abp-progress-bar type="Warning" value="25"> %25 </abp-progress-bar>
<abp-progress-bar type="Success" value="40" strip="true"/>
<abp-progress-bar type="Dark" value="10" min-value="5" max-value="15" strip="true"> %50 </abp-progress-bar>
<abp-progress-group>
<abp-progress-part type="Success" value="25"/>
<abp-progress-part type="Danger" value="10" strip="true"> %10 </abp-progress-part>
<abp-progress-part type="Primary" value="50" animation="true" strip="true" />
</abp-progress-group>
````
## Demo
参阅[进度条demo页面](https://bootstrap-taghelpers.abp.io/Components/Progress-Bars)查看示例.
## Attributes
### value
指定当前进度条的进度.
### type
指定进度条的背景颜色. 应为下列值之一:
* `Default` (默认值)
* `Secondary`
* `Success`
* `Danger`
* `Warning`
* `Info`
* `Light`
* `Dark`
### min-value
进度条的最小值. 默认值是0。
### max-value
进度条的最大值. 默认值是100.
### strip
指定进度条的背景样式是否被去除. 应为以下值之一:
* `false` (默认值)
* `true`
### animation
指定进度条的背景样式是否为动画. 应为以下值之一:
* `false` (默认值)
* `true`

91
docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Tabs.md

@ -0,0 +1,91 @@
# 标签页
## 介绍
`abp-tab` 是从引导标签元素派生的基本标签导航内容容器.
基本用法:
````xml
<abp-tabs>
<abp-tab title="Home">
Content_Home
</abp-tab>
<abp-tab-link title="Link" href="#" />
<abp-tab title="profile">
Content_Profile
</abp-tab>
<abp-tab-dropdown title="Contact" name="ContactDropdown">
<abp-tab title="Contact 1" parent-dropdown-name="ContactDropdown">
Content_1_Content
</abp-tab>
<abp-tab title="Contact 2" parent-dropdown-name="ContactDropdown">
Content_2_Content
</abp-tab>
</abp-tab-dropdown>
</abp-tabs>
````
## Demo
参阅[标签页demo页面](https://bootstrap-taghelpers.abp.io/Components/Tabs)查看示例.
## abp-tab Attributes
- **title**: 设置标签页菜单文字.
- **name:** 设置生成元素的"id"属性. 默认值为Guid. 除非使用Jquery更改或修改了选项卡,否则不需要.
- **active**: 设置active标签页.
示例:
````xml
<abp-tabs name="TabId">
<abp-tab name="nav-home" title="Home">
Content_Home
</abp-tab>
<abp-tab name="nav-profile" active="true" title="profile">
Content_Profile
</abp-tab>
<abp-tab name="nav-contact" title="Contact">
Content_Contact
</abp-tab>
</abp-tabs>
````
### Pills
示例:
````xml
<abp-tabs tab-style="Pill">
<abp-tab title="Home">
Content_Home
</abp-tab>
<abp-tab title="profile">
Content_Profile
</abp-tab>
<abp-tab title="Contact">
Content_Contact
</abp-tab>
</abp-tabs>
````
### Vertical
**vertical-header-size**: 设置标签标题的列宽.
示例:
````xml
<abp-tabs tab-style="PillVertical" vertical-header-size="_2" >
<abp-tab active="true" title="Home">
Content_Home
</abp-tab>
<abp-tab title="profile">
Content_Profile
</abp-tab>
<abp-tab title="Contact">
Content_Contact
</abp-tab>
</abp-tabs>
````

33
docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Tooltips.md

@ -0,0 +1,33 @@
# 提示工具
## 介绍
`abp-tooltip` 是提示工具的abp标签.
基本用法:
````xml
<abp-button abp-tooltip="Tooltip">
Tooltip Default
</abp-button>
<abp-button abp-tooltip-top="Tooltip">
Tooltip on top
</abp-button>
<abp-button abp-tooltip-right="Tooltip">
Tooltip on right
</abp-button>
<abp-button abp-tooltip-bottom="Tooltip">
Tooltip on bottom
</abp-button>
<abp-button disabled="true" abp-tooltip="Tooltip">
Disabled button Tooltip
</abp-button>
````
## Demo
参阅[提示工具demo页面](https://bootstrap-taghelpers.abp.io/Components/Tooltips)查看示例.

BIN
docs/zh-Hans/UI/AspNetCore/Tag-Helpers/fa-address-card.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

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

@ -5,6 +5,7 @@
"items": [ "items": [
{ {
"text": "从启动模板开始", "text": "从启动模板开始",
"path": "Getting-Started-With-Startup-Templates.md",
"items": [ "items": [
{ {
"text": "ASP.NET Core MVC 模板", "text": "ASP.NET Core MVC 模板",
@ -282,6 +283,10 @@
"text": "仪表板和小部件(Widget)系统", "text": "仪表板和小部件(Widget)系统",
"path": "UI/AspNetCore/Widgets.md" "path": "UI/AspNetCore/Widgets.md"
}, },
{
"text": "自定义/扩展UI",
"path": "UI/AspNetCore/Customization-User-Interface.md"
},
{ {
"text": "主题化", "text": "主题化",
"path": "UI/AspNetCore/Theming.md" "path": "UI/AspNetCore/Theming.md"

16
framework/Volo.Abp.sln

@ -273,7 +273,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Mvc.UI.
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo", "test\Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo\Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo.csproj", "{0C498CF2-D052-4BF7-AD35-509A90F69707}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo", "test\Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo\Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo.csproj", "{0C498CF2-D052-4BF7-AD35-509A90F69707}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Http.Client.IdentityModel.Web.Tests", "test\Volo.Abp.Http.Client.IdentityModel.Web.Tests\Volo.Abp.Http.Client.IdentityModel.Web.Tests.csproj", "{E1963439-2BE5-4DB5-8438-2A9A792A1ADA}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Http.Client.IdentityModel.Web.Tests", "test\Volo.Abp.Http.Client.IdentityModel.Web.Tests\Volo.Abp.Http.Client.IdentityModel.Web.Tests.csproj", "{E1963439-2BE5-4DB5-8438-2A9A792A1ADA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.ObjectExtending", "src\Volo.Abp.ObjectExtending\Volo.Abp.ObjectExtending.csproj", "{D1815C77-16D6-4F99-8814-69065CD89FB3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.ObjectExtending.Tests", "test\Volo.Abp.ObjectExtending.Tests\Volo.Abp.ObjectExtending.Tests.csproj", "{17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -817,6 +821,14 @@ Global
{E1963439-2BE5-4DB5-8438-2A9A792A1ADA}.Debug|Any CPU.Build.0 = Debug|Any CPU {E1963439-2BE5-4DB5-8438-2A9A792A1ADA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E1963439-2BE5-4DB5-8438-2A9A792A1ADA}.Release|Any CPU.ActiveCfg = Release|Any CPU {E1963439-2BE5-4DB5-8438-2A9A792A1ADA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E1963439-2BE5-4DB5-8438-2A9A792A1ADA}.Release|Any CPU.Build.0 = Release|Any CPU {E1963439-2BE5-4DB5-8438-2A9A792A1ADA}.Release|Any CPU.Build.0 = Release|Any CPU
{D1815C77-16D6-4F99-8814-69065CD89FB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D1815C77-16D6-4F99-8814-69065CD89FB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D1815C77-16D6-4F99-8814-69065CD89FB3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D1815C77-16D6-4F99-8814-69065CD89FB3}.Release|Any CPU.Build.0 = Release|Any CPU
{17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -956,6 +968,8 @@ Global
{29E42ADB-85F8-44AE-A9B0-078F84C1B866} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {29E42ADB-85F8-44AE-A9B0-078F84C1B866} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{0C498CF2-D052-4BF7-AD35-509A90F69707} = {447C8A77-E5F0-4538-8687-7383196D04EA} {0C498CF2-D052-4BF7-AD35-509A90F69707} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{E1963439-2BE5-4DB5-8438-2A9A792A1ADA} = {447C8A77-E5F0-4538-8687-7383196D04EA} {E1963439-2BE5-4DB5-8438-2A9A792A1ADA} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{D1815C77-16D6-4F99-8814-69065CD89FB3} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5} = {447C8A77-E5F0-4538-8687-7383196D04EA}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5}

11
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Controllers/ErrorController.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
using Localization.Resources.AbpUi; using Localization.Resources.AbpUi;
using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -7,6 +8,7 @@ using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.ExceptionHandling; using Volo.Abp.AspNetCore.ExceptionHandling;
using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Views.Error; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Views.Error;
using Volo.Abp.ExceptionHandling;
namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Controllers namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Controllers
{ {
@ -16,20 +18,23 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Controllers
private readonly IHttpExceptionStatusCodeFinder _statusCodeFinder; private readonly IHttpExceptionStatusCodeFinder _statusCodeFinder;
private readonly IStringLocalizer<AbpUiResource> _localizer; private readonly IStringLocalizer<AbpUiResource> _localizer;
private readonly AbpErrorPageOptions _abpErrorPageOptions; private readonly AbpErrorPageOptions _abpErrorPageOptions;
private readonly IExceptionNotifier _exceptionNotifier;
public ErrorController( public ErrorController(
IExceptionToErrorInfoConverter exceptionToErrorInfoConverter, IExceptionToErrorInfoConverter exceptionToErrorInfoConverter,
IHttpExceptionStatusCodeFinder httpExceptionStatusCodeFinder, IHttpExceptionStatusCodeFinder httpExceptionStatusCodeFinder,
IOptions<AbpErrorPageOptions> abpErrorPageOptions, IOptions<AbpErrorPageOptions> abpErrorPageOptions,
IStringLocalizer<AbpUiResource> localizer) IStringLocalizer<AbpUiResource> localizer,
IExceptionNotifier exceptionNotifier)
{ {
_errorInfoConverter = exceptionToErrorInfoConverter; _errorInfoConverter = exceptionToErrorInfoConverter;
_statusCodeFinder = httpExceptionStatusCodeFinder; _statusCodeFinder = httpExceptionStatusCodeFinder;
_localizer = localizer; _localizer = localizer;
_exceptionNotifier = exceptionNotifier;
_abpErrorPageOptions = abpErrorPageOptions.Value; _abpErrorPageOptions = abpErrorPageOptions.Value;
} }
public IActionResult Index(int httpStatusCode) public async Task<IActionResult> Index(int httpStatusCode)
{ {
var exHandlerFeature = HttpContext.Features.Get<IExceptionHandlerFeature>(); var exHandlerFeature = HttpContext.Features.Get<IExceptionHandlerFeature>();
@ -37,6 +42,8 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Controllers
? exHandlerFeature.Error ? exHandlerFeature.Error
: new Exception(_localizer["UnhandledException"]); : new Exception(_localizer["UnhandledException"]);
await _exceptionNotifier.NotifyAsync(new ExceptionNotificationContext(exception));
var errorInfo = _errorInfoConverter.Convert(exception); var errorInfo = _errorInfoConverter.Convert(exception);
if (httpStatusCode == 0) if (httpStatusCode == 0)

18
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionFilter.cs

@ -1,18 +1,21 @@
using System; using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.AspNetCore.ExceptionHandling; using Volo.Abp.AspNetCore.ExceptionHandling;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.ExceptionHandling;
using Volo.Abp.Http; using Volo.Abp.Http;
using Volo.Abp.Json; using Volo.Abp.Json;
namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
{ {
public class AbpExceptionFilter : IExceptionFilter, ITransientDependency public class AbpExceptionFilter : IAsyncExceptionFilter, ITransientDependency
{ {
public ILogger<AbpExceptionFilter> Logger { get; set; } public ILogger<AbpExceptionFilter> Logger { get; set; }
@ -32,14 +35,14 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
Logger = NullLogger<AbpExceptionFilter>.Instance; Logger = NullLogger<AbpExceptionFilter>.Instance;
} }
public virtual void OnException(ExceptionContext context) public async Task OnExceptionAsync(ExceptionContext context)
{ {
if (!ShouldHandleException(context)) if (!ShouldHandleException(context))
{ {
return; return;
} }
HandleAndWrapException(context); await HandleAndWrapException(context);
} }
protected virtual bool ShouldHandleException(ExceptionContext context) protected virtual bool ShouldHandleException(ExceptionContext context)
@ -65,7 +68,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
return false; return false;
} }
protected virtual void HandleAndWrapException(ExceptionContext context) protected virtual async Task HandleAndWrapException(ExceptionContext context)
{ {
//TODO: Trigger an AbpExceptionHandled event or something like that. //TODO: Trigger an AbpExceptionHandled event or something like that.
@ -82,6 +85,13 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling
Logger.LogWithLevel(logLevel, _jsonSerializer.Serialize(remoteServiceErrorInfo, indented: true)); Logger.LogWithLevel(logLevel, _jsonSerializer.Serialize(remoteServiceErrorInfo, indented: true));
Logger.LogException(context.Exception, logLevel); Logger.LogException(context.Exception, logLevel);
await context.HttpContext
.RequestServices
.GetRequiredService<IExceptionNotifier>()
.NotifyAsync(
new ExceptionNotificationContext(context.Exception)
);
context.Exception = null; //Handled! context.Exception = null; //Handled!
} }
} }

8
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/AbpExceptionHandlingMiddleware.cs

@ -7,6 +7,7 @@ using Microsoft.Net.Http.Headers;
using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Uow; using Volo.Abp.AspNetCore.Uow;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.ExceptionHandling;
using Volo.Abp.Http; using Volo.Abp.Http;
using Volo.Abp.Json; using Volo.Abp.Json;
@ -73,6 +74,13 @@ namespace Volo.Abp.AspNetCore.ExceptionHandling
) )
) )
); );
await httpContext
.RequestServices
.GetRequiredService<IExceptionNotifier>()
.NotifyAsync(
new ExceptionNotificationContext(exception)
);
} }
private Task ClearCacheHeaders(object state) private Task ClearCacheHeaders(object state)

37
framework/src/Volo.Abp.AutoMapper/AutoMapper/AbpAutoMapperExtensibleDtoExtensions.cs

@ -0,0 +1,37 @@
using System.Collections.Generic;
using Volo.Abp.Data;
using Volo.Abp.ObjectExtending;
namespace AutoMapper
{
public static class AbpAutoMapperExtensibleDtoExtensions
{
public static IMappingExpression<TSource, TDestination> MapExtraProperties<TSource, TDestination>(
this IMappingExpression<TSource, TDestination> mappingExpression,
MappingPropertyDefinitionChecks? definitionChecks = null)
where TDestination : IHasExtraProperties
where TSource : IHasExtraProperties
{
return mappingExpression
.ForMember(
x => x.ExtraProperties,
y => y.MapFrom(
(source, destination, extraProps) =>
{
var result = extraProps.IsNullOrEmpty()
? new Dictionary<string, object>()
: new Dictionary<string, object>(extraProps);
HasExtraPropertiesObjectExtendingExtensions
.MapExtraPropertiesTo<TSource, TDestination>(
source.ExtraProperties,
result,
definitionChecks
);
return result;
})
);
}
}
}

1
framework/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj

@ -15,6 +15,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Volo.Abp.ObjectExtending\Volo.Abp.ObjectExtending.csproj" />
<ProjectReference Include="..\Volo.Abp.ObjectMapping\Volo.Abp.ObjectMapping.csproj" /> <ProjectReference Include="..\Volo.Abp.ObjectMapping\Volo.Abp.ObjectMapping.csproj" />
</ItemGroup> </ItemGroup>

5
framework/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperModule.cs

@ -3,11 +3,14 @@ using AutoMapper;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
using Volo.Abp.ObjectExtending;
using Volo.Abp.ObjectMapping; using Volo.Abp.ObjectMapping;
namespace Volo.Abp.AutoMapper namespace Volo.Abp.AutoMapper
{ {
[DependsOn(typeof(AbpObjectMappingModule))] [DependsOn(
typeof(AbpObjectMappingModule),
typeof(AbpObjectExtendingModule))]
public class AbpAutoMapperModule : AbpModule public class AbpAutoMapperModule : AbpModule
{ {
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)

6
framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJobExecuter.cs

@ -3,7 +3,9 @@ using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.ExceptionHandling;
namespace Volo.Abp.BackgroundJobs namespace Volo.Abp.BackgroundJobs
{ {
@ -51,6 +53,10 @@ namespace Volo.Abp.BackgroundJobs
{ {
Logger.LogException(ex); Logger.LogException(ex);
await context.ServiceProvider
.GetRequiredService<IExceptionNotifier>()
.NotifyAsync(new ExceptionNotificationContext(ex));
throw new BackgroundJobExecutionException("A background job execution is failed. See inner exception for details.", ex) throw new BackgroundJobExecutionException("A background job execution is failed. See inner exception for details.", ex)
{ {
JobType = context.JobType.AssemblyQualifiedName, JobType = context.JobType.AssemblyQualifiedName,

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

@ -9,6 +9,7 @@ using Microsoft.Extensions.Options;
using Nito.AsyncEx; using Nito.AsyncEx;
using RabbitMQ.Client; using RabbitMQ.Client;
using RabbitMQ.Client.Events; using RabbitMQ.Client.Events;
using Volo.Abp.ExceptionHandling;
using Volo.Abp.RabbitMQ; using Volo.Abp.RabbitMQ;
using Volo.Abp.Threading; using Volo.Abp.Threading;
@ -31,6 +32,7 @@ namespace Volo.Abp.BackgroundJobs.RabbitMQ
protected IRabbitMqSerializer Serializer { get; } protected IRabbitMqSerializer Serializer { get; }
protected IBackgroundJobExecuter JobExecuter { get; } protected IBackgroundJobExecuter JobExecuter { get; }
protected IServiceScopeFactory ServiceScopeFactory { get; } protected IServiceScopeFactory ServiceScopeFactory { get; }
protected IExceptionNotifier ExceptionNotifier { get; }
protected SemaphoreSlim SyncObj = new SemaphoreSlim(1, 1); protected SemaphoreSlim SyncObj = new SemaphoreSlim(1, 1);
protected bool IsDiposed { get; private set; } protected bool IsDiposed { get; private set; }
@ -41,13 +43,15 @@ namespace Volo.Abp.BackgroundJobs.RabbitMQ
IChannelPool channelPool, IChannelPool channelPool,
IRabbitMqSerializer serializer, IRabbitMqSerializer serializer,
IBackgroundJobExecuter jobExecuter, IBackgroundJobExecuter jobExecuter,
IServiceScopeFactory serviceScopeFactory) IServiceScopeFactory serviceScopeFactory,
IExceptionNotifier exceptionNotifier)
{ {
AbpBackgroundJobOptions = backgroundJobOptions.Value; AbpBackgroundJobOptions = backgroundJobOptions.Value;
AbpRabbitMqBackgroundJobOptions = rabbitMqAbpBackgroundJobOptions.Value; AbpRabbitMqBackgroundJobOptions = rabbitMqAbpBackgroundJobOptions.Value;
Serializer = serializer; Serializer = serializer;
JobExecuter = jobExecuter; JobExecuter = jobExecuter;
ServiceScopeFactory = serviceScopeFactory; ServiceScopeFactory = serviceScopeFactory;
ExceptionNotifier = exceptionNotifier;
ChannelPool = channelPool; ChannelPool = channelPool;
JobConfiguration = AbpBackgroundJobOptions.GetJob(typeof(TArgs)); JobConfiguration = AbpBackgroundJobOptions.GetJob(typeof(TArgs));

19
framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/AsyncPeriodicBackgroundWorkerBase.cs

@ -3,6 +3,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Volo.Abp.ExceptionHandling;
using Volo.Abp.Threading; using Volo.Abp.Threading;
namespace Volo.Abp.BackgroundWorkers namespace Volo.Abp.BackgroundWorkers
@ -35,18 +36,24 @@ namespace Volo.Abp.BackgroundWorkers
private void Timer_Elapsed(object sender, System.EventArgs e) private void Timer_Elapsed(object sender, System.EventArgs e)
{ {
try using (var scope = ServiceScopeFactory.CreateScope())
{ {
using (var scope = ServiceScopeFactory.CreateScope()) try
{ {
AsyncHelper.RunSync( AsyncHelper.RunSync(
() => DoWorkAsync(new PeriodicBackgroundWorkerContext(scope.ServiceProvider)) () => DoWorkAsync(new PeriodicBackgroundWorkerContext(scope.ServiceProvider))
); );
} }
} catch (Exception ex)
catch (Exception ex) {
{ AsyncHelper.RunSync(
Logger.LogException(ex); () => scope.ServiceProvider
.GetRequiredService<IExceptionNotifier>()
.NotifyAsync(new ExceptionNotificationContext(ex))
);
Logger.LogException(ex);
}
} }
} }

18
framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/PeriodicBackgroundWorkerBase.cs

@ -3,6 +3,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Volo.Abp.ExceptionHandling;
using Volo.Abp.Threading; using Volo.Abp.Threading;
namespace Volo.Abp.BackgroundWorkers namespace Volo.Abp.BackgroundWorkers
@ -38,16 +39,21 @@ namespace Volo.Abp.BackgroundWorkers
private void Timer_Elapsed(object sender, System.EventArgs e) private void Timer_Elapsed(object sender, System.EventArgs e)
{ {
try using (var scope = ServiceScopeFactory.CreateScope())
{ {
using (var scope = ServiceScopeFactory.CreateScope()) try
{ {
DoWork(new PeriodicBackgroundWorkerContext(scope.ServiceProvider)); DoWork(new PeriodicBackgroundWorkerContext(scope.ServiceProvider));
} }
} catch (Exception ex)
catch (Exception ex) {
{ scope.ServiceProvider
Logger.LogException(ex); .GetRequiredService<IExceptionNotifier>()
.NotifyAsync(new ExceptionNotificationContext(ex));
Logger.LogException(ex);
}
} }
} }

44
framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs

@ -2,10 +2,13 @@
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Nito.AsyncEx; using Nito.AsyncEx;
using Volo.Abp.DependencyInjection;
using Volo.Abp.ExceptionHandling;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
using Volo.Abp.Threading; using Volo.Abp.Threading;
@ -23,12 +26,14 @@ namespace Volo.Abp.Caching
IDistributedCache cache, IDistributedCache cache,
ICancellationTokenProvider cancellationTokenProvider, ICancellationTokenProvider cancellationTokenProvider,
IDistributedCacheSerializer serializer, IDistributedCacheSerializer serializer,
IDistributedCacheKeyNormalizer keyNormalizer) : base( IDistributedCacheKeyNormalizer keyNormalizer,
IHybridServiceScopeFactory serviceScopeFactory) : base(
distributedCacheOption: distributedCacheOption, distributedCacheOption: distributedCacheOption,
cache: cache, cache: cache,
cancellationTokenProvider: cancellationTokenProvider, cancellationTokenProvider: cancellationTokenProvider,
serializer: serializer, serializer: serializer,
keyNormalizer: keyNormalizer) keyNormalizer: keyNormalizer,
serviceScopeFactory: serviceScopeFactory)
{ {
} }
@ -56,6 +61,8 @@ namespace Volo.Abp.Caching
protected IDistributedCacheKeyNormalizer KeyNormalizer { get; } protected IDistributedCacheKeyNormalizer KeyNormalizer { get; }
protected IHybridServiceScopeFactory ServiceScopeFactory { get; }
protected SemaphoreSlim SyncSemaphore { get; } protected SemaphoreSlim SyncSemaphore { get; }
protected DistributedCacheEntryOptions DefaultCacheOptions; protected DistributedCacheEntryOptions DefaultCacheOptions;
@ -67,7 +74,8 @@ namespace Volo.Abp.Caching
IDistributedCache cache, IDistributedCache cache,
ICancellationTokenProvider cancellationTokenProvider, ICancellationTokenProvider cancellationTokenProvider,
IDistributedCacheSerializer serializer, IDistributedCacheSerializer serializer,
IDistributedCacheKeyNormalizer keyNormalizer) IDistributedCacheKeyNormalizer keyNormalizer,
IHybridServiceScopeFactory serviceScopeFactory)
{ {
_distributedCacheOption = distributedCacheOption.Value; _distributedCacheOption = distributedCacheOption.Value;
Cache = cache; Cache = cache;
@ -75,6 +83,7 @@ namespace Volo.Abp.Caching
Logger = NullLogger<DistributedCache<TCacheItem, TCacheKey>>.Instance; Logger = NullLogger<DistributedCache<TCacheItem, TCacheKey>>.Instance;
Serializer = serializer; Serializer = serializer;
KeyNormalizer = keyNormalizer; KeyNormalizer = keyNormalizer;
ServiceScopeFactory = serviceScopeFactory;
SyncSemaphore = new SemaphoreSlim(1, 1); SyncSemaphore = new SemaphoreSlim(1, 1);
@ -139,7 +148,7 @@ namespace Volo.Abp.Caching
{ {
if (hideErrors == true) if (hideErrors == true)
{ {
Logger.LogException(ex, LogLevel.Warning); AsyncHelper.RunSync(() => HandleExceptionAsync(ex));
return null; return null;
} }
@ -181,7 +190,7 @@ namespace Volo.Abp.Caching
{ {
if (hideErrors == true) if (hideErrors == true)
{ {
Logger.LogException(ex, LogLevel.Warning); await HandleExceptionAsync(ex);
return null; return null;
} }
@ -298,7 +307,7 @@ namespace Volo.Abp.Caching
{ {
if (hideErrors == true) if (hideErrors == true)
{ {
Logger.LogException(ex, LogLevel.Warning); AsyncHelper.RunSync(() => HandleExceptionAsync(ex));
return; return;
} }
@ -337,7 +346,7 @@ namespace Volo.Abp.Caching
{ {
if (hideErrors == true) if (hideErrors == true)
{ {
Logger.LogException(ex, LogLevel.Warning); await HandleExceptionAsync(ex);
return; return;
} }
@ -364,7 +373,7 @@ namespace Volo.Abp.Caching
{ {
if (hideErrors == true) if (hideErrors == true)
{ {
Logger.LogException(ex, LogLevel.Warning); AsyncHelper.RunSync(() => HandleExceptionAsync(ex));
return; return;
} }
@ -393,7 +402,7 @@ namespace Volo.Abp.Caching
{ {
if (hideErrors == true) if (hideErrors == true)
{ {
Logger.LogException(ex, LogLevel.Warning); await HandleExceptionAsync(ex);
return; return;
} }
@ -420,7 +429,8 @@ namespace Volo.Abp.Caching
{ {
if (hideErrors == true) if (hideErrors == true)
{ {
Logger.LogException(ex, LogLevel.Warning); AsyncHelper.RunSync(() => HandleExceptionAsync(ex));
return;
} }
throw; throw;
@ -449,12 +459,24 @@ namespace Volo.Abp.Caching
{ {
if (hideErrors == true) if (hideErrors == true)
{ {
Logger.LogException(ex, LogLevel.Warning); await HandleExceptionAsync(ex);
return; return;
} }
throw; throw;
} }
} }
protected virtual async Task HandleExceptionAsync(Exception ex)
{
Logger.LogException(ex, LogLevel.Warning);
using (var scope = ServiceScopeFactory.CreateScope())
{
await scope.ServiceProvider
.GetRequiredService<IExceptionNotifier>()
.NotifyAsync(new ExceptionNotificationContext(ex, LogLevel.Warning));
}
}
} }
} }

43
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GenerateProxyCommand.cs

@ -57,9 +57,19 @@ namespace Volo.Abp.Cli.Commands
var uiFramework = GetUiFramework(commandLineArgs); var uiFramework = GetUiFramework(commandLineArgs);
WebClient client = new WebClient(); WebClient client = new WebClient();
string json = client.DownloadString(apiUrl); string json = "";
//var sr = File.OpenText("api-definition.json"); try
//var json = sr.ReadToEnd(); {
json = client.DownloadString(apiUrl);
}
catch (Exception ex)
{
throw new CliUsageException(
"Cannot connect to the host {" + apiUrl + "}! Check that the host is up and running." +
Environment.NewLine + Environment.NewLine +
GetUsageInfo()
);
}
Logger.LogInformation("Downloading api definition..."); Logger.LogInformation("Downloading api definition...");
Logger.LogInformation("Api Url: " + apiUrl); Logger.LogInformation("Api Url: " + apiUrl);
@ -107,7 +117,7 @@ namespace Volo.Abp.Cli.Commands
serviceFileText.AppendLine(""); serviceFileText.AppendLine("");
serviceFileText.AppendLine("@Injectable({providedIn: 'root'})"); serviceFileText.AppendLine("@Injectable({providedIn: 'root'})");
serviceFileText.AppendLine("export class [controllerName]Service {"); serviceFileText.AppendLine("export class [controllerName]Service {");
serviceFileText.AppendLine(" apiName = '"+ apiName + "';"); serviceFileText.AppendLine(" apiName = '" + apiName + "';");
serviceFileText.AppendLine(""); serviceFileText.AppendLine("");
serviceFileText.AppendLine(" constructor(private restService: RestService) {}"); serviceFileText.AppendLine(" constructor(private restService: RestService) {}");
serviceFileText.AppendLine(""); serviceFileText.AppendLine("");
@ -125,7 +135,7 @@ namespace Volo.Abp.Cli.Commands
actionName = (char.ToLower(actionName[0]) + actionName.Substring(1)).Replace("Async", "").Replace("Controller", ""); actionName = (char.ToLower(actionName[0]) + actionName.Substring(1)).Replace("Async", "").Replace("Controller", "");
var returnValueType = (string)action["returnValue"]["type"]; var returnValueType = (string)action["returnValue"]["type"];
var parameters = action["parameters"]; var parameters = action["parameters"];
var parametersText = new StringBuilder(); var parametersText = new StringBuilder();
@ -140,7 +150,7 @@ namespace Volo.Abp.Cli.Commands
var bindingSourceId = (string)parameter["bindingSourceId"]; var bindingSourceId = (string)parameter["bindingSourceId"];
bindingSourceId = char.ToLower(bindingSourceId[0]) + bindingSourceId.Substring(1); bindingSourceId = char.ToLower(bindingSourceId[0]) + bindingSourceId.Substring(1);
var name = (string)parameter["name"]; var name = (string)parameter["name"];
var typeSimple = (string)parameter["typeSimple"]; var typeSimple = (string)parameter["typeSimple"];
var typeArray = ((string)parameter["type"]).Split("."); var typeArray = ((string)parameter["type"]).Split(".");
var type = (typeArray[typeArray.Length - 1]).TrimEnd('>'); var type = (typeArray[typeArray.Length - 1]).TrimEnd('>');
@ -201,7 +211,8 @@ namespace Volo.Abp.Cli.Commands
{ {
secondTypeList.Add(type); secondTypeList.Add(type);
} }
else { else
{
firstTypeList.Add(type); firstTypeList.Add(type);
} }
break; break;
@ -217,7 +228,7 @@ namespace Volo.Abp.Cli.Commands
var parameterItemModelPath = $"src/app/{rootPath}/shared/models/{parameterItemModelName}"; var parameterItemModelPath = $"src/app/{rootPath}/shared/models/{parameterItemModelName}";
if (parameterItem.BindingSourceId == "body" && !File.Exists(parameterItemModelPath)) if (parameterItem.BindingSourceId == "body" && !File.Exists(parameterItemModelPath))
{ {
parameterItem.Type = "any"; parameterItem.Type = "any";
} }
parametersIndex++; parametersIndex++;
@ -248,7 +259,7 @@ namespace Volo.Abp.Cli.Commands
var firstType = firstTypeArray[firstTypeArray.Length - 1]; var firstType = firstTypeArray[firstTypeArray.Length - 1];
var secondTypeArray = returnValueType.Split("<")[1].Split("."); var secondTypeArray = returnValueType.Split("<")[1].Split(".");
var secondType = secondTypeArray[secondTypeArray.Length - 1].TrimEnd('>'); var secondType = secondTypeArray[secondTypeArray.Length - 1].TrimEnd('>');
var secondTypeModelName = secondType.PascalToKebabCase() + ".ts"; var secondTypeModelName = secondType.PascalToKebabCase() + ".ts";
var secondTypeModelPath = $"src/app/{rootPath}/shared/models/{secondTypeModelName}"; var secondTypeModelPath = $"src/app/{rootPath}/shared/models/{secondTypeModelName}";
@ -405,7 +416,7 @@ namespace Volo.Abp.Cli.Commands
} }
private static string CreateType(JObject data, string returnValueType, string rootPath, List<string> modelIndexList) private static string CreateType(JObject data, string returnValueType, string rootPath, List<string> modelIndexList)
{ {
var type = data["types"][returnValueType]; var type = data["types"][returnValueType];
if (type == null) if (type == null)
@ -435,9 +446,9 @@ namespace Volo.Abp.Cli.Commands
} }
var typeModelName = typeName.Replace("<", "").Replace(">", "").Replace("?","").PascalToKebabCase() + ".ts"; var typeModelName = typeName.Replace("<", "").Replace(">", "").Replace("?", "").PascalToKebabCase() + ".ts";
var path = $"src/app/{rootPath}/shared/models/{typeModelName}"; var path = $"src/app/{rootPath}/shared/models/{typeModelName}";
var modelFileText = new StringBuilder(); var modelFileText = new StringBuilder();
@ -474,7 +485,7 @@ namespace Volo.Abp.Cli.Commands
if (!string.IsNullOrWhiteSpace(modelIndex)) if (!string.IsNullOrWhiteSpace(modelIndex))
{ {
modelIndexList.Add(modelIndex); modelIndexList.Add(modelIndex);
} }
} }
} }
@ -501,7 +512,7 @@ namespace Volo.Abp.Cli.Commands
{ {
var propertyName = (string)property["name"]; var propertyName = (string)property["name"];
propertyName = (char.ToLower(propertyName[0]) + propertyName.Substring(1)); propertyName = (char.ToLower(propertyName[0]) + propertyName.Substring(1));
var typeSimple = (string)property["typeSimple"]; var typeSimple = (string)property["typeSimple"];
var modelIndex = CreateType(data, (string)property["type"], rootPath, modelIndexList); var modelIndex = CreateType(data, (string)property["type"], rootPath, modelIndexList);
@ -536,11 +547,11 @@ namespace Volo.Abp.Cli.Commands
) )
{ {
var typeSimpleModelName = typeSimple.PascalToKebabCase() + ".ts"; var typeSimpleModelName = typeSimple.PascalToKebabCase() + ".ts";
var modelPath = $"src/app/{rootPath}/shared/models/{typeSimpleModelName}"; var modelPath = $"src/app/{rootPath}/shared/models/{typeSimpleModelName}";
if (!File.Exists(modelPath)) if (!File.Exists(modelPath))
{ {
typeSimple = "any" + (typeSimple.Contains("[]") ? "[]" : ""); typeSimple = "any" + (typeSimple.Contains("[]") ? "[]" : "");
} }
} }
if (propertyList.Any(p => p.Key == baseTypeName && p.Value.Any(q => q.Key == propertyName && q.Value == typeSimple))) if (propertyList.Any(p => p.Key == baseTypeName && p.Value.Any(q => q.Key == propertyName && q.Value == typeSimple)))

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

@ -73,6 +73,12 @@ namespace Volo.Abp.Cli.Commands
Logger.LogInformation("UI Framework: " + uiFramework); Logger.LogInformation("UI Framework: " + uiFramework);
} }
var connectionString = GetConnectionString(commandLineArgs);
if (connectionString != null)
{
Logger.LogInformation("Connection string: " + connectionString);
}
var mobileApp = GetMobilePreference(commandLineArgs); var mobileApp = GetMobilePreference(commandLineArgs);
if (mobileApp != MobileApp.None) if (mobileApp != MobileApp.None)
{ {
@ -94,7 +100,7 @@ namespace Volo.Abp.Cli.Commands
var createSolutionFolder = (commandLineArgs.Options.GetOrNull(Options.CreateSolutionFolder.Short, Options.CreateSolutionFolder.Long) ?? "true").ToLowerInvariant() != "false"; var createSolutionFolder = (commandLineArgs.Options.GetOrNull(Options.CreateSolutionFolder.Short, Options.CreateSolutionFolder.Long) ?? "true").ToLowerInvariant() != "false";
if (!createSolutionFolder) if (!createSolutionFolder)
{ {
Logger.LogInformation("Create Solution Folder: false"); Logger.LogInformation("Create Solution Folder: no");
} }
var outputFolder = commandLineArgs.Options.GetOrNull(Options.OutputFolder.Short, Options.OutputFolder.Long); var outputFolder = commandLineArgs.Options.GetOrNull(Options.OutputFolder.Short, Options.OutputFolder.Long);
@ -106,10 +112,7 @@ namespace Volo.Abp.Cli.Commands
Path.Combine(outputFolderRoot, SolutionName.Parse(projectName).FullName) : Path.Combine(outputFolderRoot, SolutionName.Parse(projectName).FullName) :
outputFolderRoot; outputFolderRoot;
if (!Directory.Exists(outputFolder)) Volo.Abp.IO.DirectoryHelper.CreateIfNotExists(outputFolder);
{
Directory.CreateDirectory(outputFolder);
}
Logger.LogInformation("Output folder: " + outputFolder); Logger.LogInformation("Output folder: " + outputFolder);
@ -125,7 +128,8 @@ namespace Volo.Abp.Cli.Commands
mobileApp, mobileApp,
gitHubLocalRepositoryPath, gitHubLocalRepositoryPath,
templateSource, templateSource,
commandLineArgs.Options commandLineArgs.Options,
connectionString
) )
); );
@ -171,6 +175,12 @@ namespace Volo.Abp.Cli.Commands
Logger.LogInformation($"'{projectName}' has been successfully created to '{outputFolder}'"); Logger.LogInformation($"'{projectName}' has been successfully created to '{outputFolder}'");
} }
private static string GetConnectionString(CommandLineArgs commandLineArgs)
{
var connectionString = commandLineArgs.Options.GetOrNull(Options.ConnectionString.Short, Options.ConnectionString.Long);
return string.IsNullOrWhiteSpace(connectionString) ? null : connectionString;
}
public string GetUsageInfo() public string GetUsageInfo()
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
@ -312,6 +322,12 @@ namespace Volo.Abp.Cli.Commands
public const string Long = "template-source"; public const string Long = "template-source";
} }
public static class ConnectionString
{
public const string Short = "cs";
public const string Long = "connection-string";
}
public static class CreateSolutionFolder public static class CreateSolutionFolder
{ {
public const string Short = "csf"; public const string Short = "csf";

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

@ -2,11 +2,16 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp.Cli.Http; using Volo.Abp.Cli.Http;
using Volo.Abp.Cli.ProjectBuilding.Templates.App;
using Volo.Abp.Cli.ProjectBuilding.Templates.MvcModule;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Http; using Volo.Abp.Http;
using Volo.Abp.IO; using Volo.Abp.IO;
@ -48,16 +53,33 @@ namespace Volo.Abp.Cli.ProjectBuilding
string templateSource = null) string templateSource = null)
{ {
DirectoryHelper.CreateIfNotExists(CliPaths.TemplateCache);
var latestVersion = await GetLatestSourceCodeVersionAsync(name, type); var latestVersion = await GetLatestSourceCodeVersionAsync(name, type);
if (version == null) if (version == null)
{ {
if (latestVersion == null)
{
Logger.LogWarning("The remote service is currently unavailable, please specify the version.");
Logger.LogWarning(string.Empty);
Logger.LogWarning("Find the following template in your cache directory: ");
Logger.LogWarning("\t Template Name\tVersion");
var templateList = GetLocalTemplates();
foreach (var cacheFile in templateList)
{
Logger.LogWarning($"\t {cacheFile.TemplateName}\t\t{cacheFile.Version}");
}
Logger.LogWarning(string.Empty);
throw new CliUsageException("Use command: abp new Acme.BookStore -v version");
}
version = latestVersion; version = latestVersion;
} }
var nugetVersion = (await GetTemplateNugetVersionAsync(name, type, version)) ?? version; var nugetVersion = (await GetTemplateNugetVersionAsync(name, type, version)) ?? version;
DirectoryHelper.CreateIfNotExists(CliPaths.TemplateCache);
if (!string.IsNullOrWhiteSpace(templateSource) && !IsNetworkSource(templateSource)) if (!string.IsNullOrWhiteSpace(templateSource) && !IsNetworkSource(templateSource))
{ {
Logger.LogInformation("Using local " + type + ": " + name + ", version: " + version); Logger.LogInformation("Using local " + type + ": " + name + ", version: " + version);
@ -122,7 +144,7 @@ namespace Volo.Abp.Cli.ProjectBuilding
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine("Error occured while getting the latest version from {0} : {1}", url, ex.Message); Console.WriteLine("Error occured while getting the latest version from {0} : {1}", url, ex.Message);
throw; return null;
} }
} }
@ -195,11 +217,30 @@ namespace Volo.Abp.Cli.ProjectBuilding
} }
} }
private static bool IsNetworkSource(string source) private bool IsNetworkSource(string source)
{ {
return source.ToLower().StartsWith("http"); return source.ToLower().StartsWith("http");
} }
private List<(string TemplateName, string Version)> GetLocalTemplates()
{
var templateList = new List<(string TemplateName, string Version)>();
var stringBuilder = new StringBuilder();
foreach (var cacheFile in Directory.GetFiles(CliPaths.TemplateCache))
{
stringBuilder.AppendLine(cacheFile);
}
var matches = Regex.Matches(stringBuilder.ToString(),$"({AppTemplate.TemplateName}|{AppProTemplate.TemplateName}|{ModuleTemplate.TemplateName}|{ModuleProTemplate.TemplateName})-(.+).zip");
foreach (Match match in matches)
{
templateList.Add((match.Groups[1].Value, match.Groups[2].Value));
}
return templateList;
}
public class SourceCodeDownloadInputDto public class SourceCodeDownloadInputDto
{ {
public string Name { get; set; } public string Name { get; set; }

40
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Analyticses/CliAnalyticsCollect.cs

@ -1,4 +1,5 @@
using System.Net.Http; using System;
using System.Net.Http;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -32,27 +33,36 @@ namespace Volo.Abp.Cli.ProjectBuilding.Analyticses
public async Task CollectAsync(CliAnalyticsCollectInputDto input) public async Task CollectAsync(CliAnalyticsCollectInputDto input)
{ {
var postData = _jsonSerializer.Serialize(input); var postData = _jsonSerializer.Serialize(input);
using (var client = new CliHttpClient()) var url = $"{CliUrls.WwwAbpIo}api/clianalytics/collect";
try
{ {
var responseMessage = await client.PostAsync( using (var client = new CliHttpClient())
$"{CliUrls.WwwAbpIo}api/clianalytics/collect",
new StringContent(postData, Encoding.UTF8, MimeTypes.Application.Json),
_cancellationTokenProvider.Token
);
if (!responseMessage.IsSuccessStatusCode)
{ {
var exceptionMessage = "Remote server returns '" + (int)responseMessage.StatusCode + "-" + responseMessage.ReasonPhrase + "'. "; var responseMessage = await client.PostAsync(
var remoteServiceErrorMessage = await _remoteServiceExceptionHandler.GetAbpRemoteServiceErrorAsync(responseMessage); url,
new StringContent(postData, Encoding.UTF8, MimeTypes.Application.Json),
_cancellationTokenProvider.Token
);
if (remoteServiceErrorMessage != null) if (!responseMessage.IsSuccessStatusCode)
{ {
exceptionMessage += remoteServiceErrorMessage; var exceptionMessage = "Remote server returns '" + (int)responseMessage.StatusCode + "-" + responseMessage.ReasonPhrase + "'. ";
} var remoteServiceErrorMessage = await _remoteServiceExceptionHandler.GetAbpRemoteServiceErrorAsync(responseMessage);
if (remoteServiceErrorMessage != null)
{
exceptionMessage += remoteServiceErrorMessage;
}
_logger.LogInformation(exceptionMessage); _logger.LogInformation(exceptionMessage);
}
} }
} }
catch (Exception ex)
{
// ignored
}
} }
} }
} }

76
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ConnectionStringChangeStep.cs

@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Volo.Abp.Cli.ProjectBuilding.Files;
using Volo.Abp.Text;
namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
{
public class ConnectionStringChangeStep : ProjectBuildPipelineStep
{
private const string DefaultConnectionStringKey = "Default";
public override void Execute(ProjectBuildContext context)
{
var appSettingsJsonFiles = context.Files.Where(f =>
f.Name.EndsWith("appsettings.json", StringComparison.OrdinalIgnoreCase))
.ToArray();
if (!appSettingsJsonFiles.Any())
{
return;
}
var newConnectionString = $"\"{DefaultConnectionStringKey}\": \"{context.BuildArgs.ConnectionString}\"";
foreach (var appSettingsJson in appSettingsJsonFiles)
{
try
{
var appSettingJsonContentWithoutBom = StringHelper.ConvertFromBytesWithoutBom(appSettingsJson.Bytes);
var jsonObject = JObject.Parse(appSettingJsonContentWithoutBom);
var connectionStringContainer = (JContainer)jsonObject?["ConnectionStrings"];
if (connectionStringContainer == null)
{
continue;
}
if (!connectionStringContainer.Any())
{
continue;
}
var connectionStrings = connectionStringContainer.ToList();
foreach (var connectionString in connectionStrings)
{
var property = ((JProperty)connectionString);
var connectionStringName = property.Name;
if (connectionStringName == DefaultConnectionStringKey)
{
var defaultConnectionString = property.ToString();
if (defaultConnectionString == null)
{
continue;
}
appSettingsJson.ReplaceText(defaultConnectionString, newConnectionString);
break;
}
}
}
catch (Exception ex)
{
Console.WriteLine("Cannot change the connection string in " + appSettingsJson.Name + ". Error: " + ex.Message);
}
}
}
}
}

5
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateProjectBuildPipelineBuilder.cs

@ -28,6 +28,11 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building
pipeline.Steps.Add(new RemoveRootFolderStep()); pipeline.Steps.Add(new RemoveRootFolderStep());
} }
if (context.BuildArgs.ConnectionString != null)
{
pipeline.Steps.Add(new ConnectionStringChangeStep());
}
pipeline.Steps.Add(new CreateProjectResultZipStep()); pipeline.Steps.Add(new CreateProjectResultZipStep());
return pipeline; return pipeline;

7
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuildArgs.cs

@ -27,6 +27,9 @@ namespace Volo.Abp.Cli.ProjectBuilding
[CanBeNull] [CanBeNull]
public string TemplateSource { get; set; } public string TemplateSource { get; set; }
[CanBeNull]
public string ConnectionString { get; set; }
[NotNull] [NotNull]
public Dictionary<string, string> ExtraProperties { get; set; } public Dictionary<string, string> ExtraProperties { get; set; }
@ -39,7 +42,8 @@ namespace Volo.Abp.Cli.ProjectBuilding
MobileApp? mobileApp = null, MobileApp? mobileApp = null,
[CanBeNull] string abpGitHubLocalRepositoryPath = null, [CanBeNull] string abpGitHubLocalRepositoryPath = null,
[CanBeNull] string templateSource = null, [CanBeNull] string templateSource = null,
Dictionary<string, string> extraProperties = null) Dictionary<string, string> extraProperties = null,
[CanBeNull] string connectionString = null)
{ {
SolutionName = Check.NotNull(solutionName, nameof(solutionName)); SolutionName = Check.NotNull(solutionName, nameof(solutionName));
TemplateName = templateName; TemplateName = templateName;
@ -50,6 +54,7 @@ namespace Volo.Abp.Cli.ProjectBuilding
AbpGitHubLocalRepositoryPath = abpGitHubLocalRepositoryPath; AbpGitHubLocalRepositoryPath = abpGitHubLocalRepositoryPath;
TemplateSource = templateSource; TemplateSource = templateSource;
ExtraProperties = extraProperties ?? new Dictionary<string, string>(); ExtraProperties = extraProperties ?? new Dictionary<string, string>();
ConnectionString = connectionString;
} }
} }
} }

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmGlobalPackagesChecker.cs

@ -9,7 +9,7 @@ namespace Volo.Abp.Cli.ProjectModification
{ {
public ILogger<NpmGlobalPackagesChecker> Logger { get; set; } public ILogger<NpmGlobalPackagesChecker> Logger { get; set; }
public NpmGlobalPackagesChecker(PackageJsonFileFinder packageJsonFileFinder) public NpmGlobalPackagesChecker()
{ {
Logger = NullLogger<NpmGlobalPackagesChecker>.Instance; Logger = NullLogger<NpmGlobalPackagesChecker>.Instance;
} }

15
framework/src/Volo.Abp.Core/Volo/Abp/Check.cs

@ -121,6 +121,21 @@ namespace Volo.Abp
return value; return value;
} }
[ContractAnnotation("type:null => halt")]
public static Type AssignableTo<TBaseType>(
Type type,
[InvokerParameterName] [NotNull] string parameterName)
{
NotNull(type, parameterName);
if (!type.IsAssignableTo<TBaseType>())
{
throw new ArgumentException($"{parameterName} (type of {type.AssemblyQualifiedName}) should be assignable to the {typeof(TBaseType).GetFullNameWithAssemblyName()}!");
}
return type;
}
public static string Length( public static string Length(
[CanBeNull] string value, [CanBeNull] string value,
[InvokerParameterName] [NotNull] string parameterName, [InvokerParameterName] [NotNull] string parameterName,

4
framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ExposedServiceExplorer.cs

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection;
namespace Volo.Abp.DependencyInjection namespace Volo.Abp.DependencyInjection
{ {
@ -17,10 +16,11 @@ namespace Volo.Abp.DependencyInjection
public static List<Type> GetExposedServices(Type type) public static List<Type> GetExposedServices(Type type)
{ {
return type return type
.GetCustomAttributes() .GetCustomAttributes(true)
.OfType<IExposedServiceTypesProvider>() .OfType<IExposedServiceTypesProvider>()
.DefaultIfEmpty(DefaultExposeServicesAttribute) .DefaultIfEmpty(DefaultExposeServicesAttribute)
.SelectMany(p => p.GetExposedServiceTypes(type)) .SelectMany(p => p.GetExposedServiceTypes(type))
.Distinct()
.ToList(); .ToList();
} }
} }

32
framework/src/Volo.Abp.Core/Volo/Abp/ExceptionHandling/ExceptionNotificationContext.cs

@ -0,0 +1,32 @@
using System;
using JetBrains.Annotations;
using Microsoft.Extensions.Logging;
namespace Volo.Abp.ExceptionHandling
{
public class ExceptionNotificationContext
{
/// <summary>
/// The exception object.
/// </summary>
[NotNull]
public Exception Exception { get; }
public LogLevel LogLevel { get; }
/// <summary>
/// True, if it is handled.
/// </summary>
public bool Handled { get; }
public ExceptionNotificationContext(
[NotNull] Exception exception,
LogLevel? logLevel = null,
bool handled = true)
{
Exception = Check.NotNull(exception, nameof(exception));
LogLevel = logLevel ?? exception.GetLogLevel();
Handled = handled;
}
}
}

41
framework/src/Volo.Abp.Core/Volo/Abp/ExceptionHandling/ExceptionNotifier.cs

@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.ExceptionHandling
{
public class ExceptionNotifier : IExceptionNotifier, ITransientDependency
{
public ILogger<ExceptionNotifier> Logger { get; set; }
protected IEnumerable<IExceptionSubscriber> ExceptionSubscribers { get; }
public ExceptionNotifier(IEnumerable<IExceptionSubscriber> exceptionSubscribers)
{
ExceptionSubscribers = exceptionSubscribers;
Logger = NullLogger<ExceptionNotifier>.Instance;
}
public virtual async Task NotifyAsync([NotNull] ExceptionNotificationContext context)
{
Check.NotNull(context, nameof(context));
foreach (var exceptionSubscriber in ExceptionSubscribers)
{
try
{
await exceptionSubscriber.HandleAsync(context);
}
catch (Exception e)
{
Logger.LogWarning($"Exception subscriber of type {exceptionSubscriber.GetType().AssemblyQualifiedName} has thrown an exception!");
Logger.LogException(e, LogLevel.Warning);
}
}
}
}
}

27
framework/src/Volo.Abp.Core/Volo/Abp/ExceptionHandling/ExceptionNotifierExtensions.cs

@ -0,0 +1,27 @@
using System;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Logging;
namespace Volo.Abp.ExceptionHandling
{
public static class ExceptionNotifierExtensions
{
public static Task NotifyAsync(
[NotNull] this IExceptionNotifier exceptionNotifier,
[NotNull] Exception exception,
LogLevel? logLevel = null,
bool handled = true)
{
Check.NotNull(exceptionNotifier, nameof(exceptionNotifier));
return exceptionNotifier.NotifyAsync(
new ExceptionNotificationContext(
exception,
logLevel,
handled
)
);
}
}
}

11
framework/src/Volo.Abp.Core/Volo/Abp/ExceptionHandling/ExceptionSubscriber.cs

@ -0,0 +1,11 @@
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.ExceptionHandling
{
[ExposeServices(typeof(IExceptionSubscriber))]
public abstract class ExceptionSubscriber : IExceptionSubscriber, ITransientDependency
{
public abstract Task HandleAsync(ExceptionNotificationContext context);
}
}

10
framework/src/Volo.Abp.Core/Volo/Abp/ExceptionHandling/IExceptionNotifier.cs

@ -0,0 +1,10 @@
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Volo.Abp.ExceptionHandling
{
public interface IExceptionNotifier
{
Task NotifyAsync([NotNull] ExceptionNotificationContext context);
}
}

10
framework/src/Volo.Abp.Core/Volo/Abp/ExceptionHandling/IExceptionSubscriber.cs

@ -0,0 +1,10 @@
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Volo.Abp.ExceptionHandling
{
public interface IExceptionSubscriber
{
Task HandleAsync([NotNull] ExceptionNotificationContext context);
}
}

61
framework/src/Volo.Abp.Core/Volo/Abp/IO/FileHelper.cs

@ -1,8 +1,10 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using Volo.Abp.Text;
namespace Volo.Abp.IO namespace Volo.Abp.IO
{ {
@ -73,6 +75,63 @@ namespace Volo.Abp.IO
} }
} }
//TODO: ReadAllLinesAsync /// <summary>
/// Opens a text file, reads all lines of the file, and then closes the file.
/// </summary>
/// <param name="path">The file to open for reading.</param>
/// <param name="encoding">Encoding of the file. Default is UTF8</param>
/// <param name="fileMode">Specifies how the operating system should open a file. Default is Open</param>
/// <param name="fileAccess">Defines constants for read, write, or read/write access to a file. Default is Read</param>
/// <param name="fileShare">Contains constants for controlling the kind of access other FileStream objects can have to the same file. Default is Read</param>
/// <param name="bufferSize">Length of StreamReader buffer. Default is 4096.</param>
/// <param name="fileOptions">Indicates FileStream options. Default is Asynchronous (The file is to be used for asynchronous reading.) and SequentialScan (The file is to be accessed sequentially from beginning to end.) </param>
/// <returns>A string containing all lines of the file.</returns>
public static async Task<string[]> ReadAllLinesAsync(string path,
Encoding encoding = null,
FileMode fileMode = FileMode.Open,
FileAccess fileAccess = FileAccess.Read,
FileShare fileShare = FileShare.Read,
int bufferSize = 4096,
FileOptions fileOptions = FileOptions.Asynchronous | FileOptions.SequentialScan)
{
if (encoding == null)
{
encoding = Encoding.UTF8;
}
var lines = new List<string>();
using (var stream = new FileStream(
path,
fileMode,
fileAccess,
fileShare,
bufferSize,
fileOptions))
{
using (var reader = new StreamReader(stream, encoding))
{
string line;
while ((line = await reader.ReadLineAsync()) != null)
{
lines.Add(line);
}
}
}
return lines.ToArray();
}
/// <summary>
/// Opens a text file, reads content without BOM
/// </summary>
/// <param name="path">The file to open for reading.</param>
/// <returns>A string containing all lines of the file.</returns>
public static async Task<string> ReadFileWithoutBomAsync(string path)
{
var content = await ReadAllBytesAsync(path);
return StringHelper.ConvertFromBytesWithoutBom(content);
}
} }
} }

37
framework/src/Volo.Abp.Core/Volo/Abp/Text/StringHelper.cs

@ -0,0 +1,37 @@
using System.Text;
namespace Volo.Abp.Text
{
public class StringHelper
{
/// <summary>
/// Converts a byte[] to string without BOM (byte order mark).
/// </summary>
/// <param name="bytes">The byte[] to be converted to string</param>
/// <param name="encoding">The encoding to get string. Default is UTF8</param>
/// <returns></returns>
public static string ConvertFromBytesWithoutBom(byte[] bytes, Encoding encoding = null)
{
if (bytes == null)
{
return null;
}
if (encoding == null)
{
encoding = Encoding.UTF8;
}
var hasBom = bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF;
if (hasBom)
{
return encoding.GetString(bytes, 3, bytes.Length - 3);
}
else
{
return encoding.GetString(bytes);
}
}
}
}

1
framework/src/Volo.Abp.Data/Volo.Abp.Data.csproj

@ -16,6 +16,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Volo.Abp.Core\Volo.Abp.Core.csproj" /> <ProjectReference Include="..\Volo.Abp.Core\Volo.Abp.Core.csproj" />
<ProjectReference Include="..\Volo.Abp.ObjectExtending\Volo.Abp.ObjectExtending.csproj" />
<ProjectReference Include="..\Volo.Abp.Uow\Volo.Abp.Uow.csproj" /> <ProjectReference Include="..\Volo.Abp.Uow\Volo.Abp.Uow.csproj" />
</ItemGroup> </ItemGroup>

4
framework/src/Volo.Abp.Data/Volo/Abp/Data/AbpDataModule.cs

@ -2,13 +2,15 @@
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
using Volo.Abp.ObjectExtending;
using Volo.Abp.Uow; using Volo.Abp.Uow;
namespace Volo.Abp.Data namespace Volo.Abp.Data
{ {
[DependsOn( [DependsOn(
typeof(AbpObjectExtendingModule),
typeof(AbpUnitOfWorkModule) typeof(AbpUnitOfWorkModule)
)] )]
public class AbpDataModule : AbpModule public class AbpDataModule : AbpModule
{ {
public override void PreConfigureServices(ServiceConfigurationContext context) public override void PreConfigureServices(ServiceConfigurationContext context)

2
framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/AuditedEntityWithUserDto.cs

@ -5,6 +5,7 @@ namespace Volo.Abp.Application.Dtos
{ {
/// <summary> /// <summary>
/// This class can be inherited by DTO classes to implement <see cref="IAuditedObject"/> interface. /// This class can be inherited by DTO classes to implement <see cref="IAuditedObject"/> interface.
/// It has the <see cref="Creator"/> and <see cref="LastModifier"/> objects as a DTOs represent the related user.
/// </summary> /// </summary>
/// <typeparam name="TUserDto">Type of the User DTO</typeparam> /// <typeparam name="TUserDto">Type of the User DTO</typeparam>
[Serializable] [Serializable]
@ -19,6 +20,7 @@ namespace Volo.Abp.Application.Dtos
/// <summary> /// <summary>
/// This class can be inherited by DTO classes to implement <see cref="IAuditedObject"/> interface. /// This class can be inherited by DTO classes to implement <see cref="IAuditedObject"/> interface.
/// It has the <see cref="Creator"/> and <see cref="LastModifier"/> objects as a DTOs represent the related user.
/// </summary> /// </summary>
/// <typeparam name="TPrimaryKey">Type of primary key</typeparam> /// <typeparam name="TPrimaryKey">Type of primary key</typeparam>
/// <typeparam name="TUserDto">Type of the User DTO</typeparam> /// <typeparam name="TUserDto">Type of the User DTO</typeparam>

4
framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/CreationAuditedEntityWithUserDto.cs

@ -5,6 +5,7 @@ namespace Volo.Abp.Application.Dtos
{ {
/// <summary> /// <summary>
/// This class can be inherited by DTO classes to implement <see cref="ICreationAuditedObject{TCreator}"/> interface. /// This class can be inherited by DTO classes to implement <see cref="ICreationAuditedObject{TCreator}"/> interface.
/// It also has the <see cref="Creator"/> object as a DTO represents the user.
/// </summary> /// </summary>
/// <typeparam name="TUserDto">Type of the User DTO</typeparam> /// <typeparam name="TUserDto">Type of the User DTO</typeparam>
[Serializable] [Serializable]
@ -14,7 +15,8 @@ namespace Volo.Abp.Application.Dtos
} }
/// <summary> /// <summary>
/// This class can be inherited by DTO classes to implement <see cref="ICreationAuditedObjectObject{TCreator}"/> interface. /// This class can be inherited by DTO classes to implement <see cref="ICreationAuditedObject{TCreator}"/> interface.
/// It also has the <see cref="Creator"/> object as a DTO represents the user.
/// </summary> /// </summary>
/// <typeparam name="TPrimaryKey">Type of primary key</typeparam> /// <typeparam name="TPrimaryKey">Type of primary key</typeparam>
/// <typeparam name="TUserDto">Type of the User DTO</typeparam> /// <typeparam name="TUserDto">Type of the User DTO</typeparam>

35
framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleAuditedEntityDto.cs

@ -0,0 +1,35 @@
using System;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
namespace Volo.Abp.Application.Dtos
{
/// <summary>
/// This class can be inherited by DTO classes to implement <see cref="IAuditedObject"/> interface.
/// It also implements the <see cref="IHasExtraProperties"/> interface.
/// </summary>
/// <typeparam name="TPrimaryKey">Type of primary key</typeparam>
[Serializable]
public abstract class ExtensibleAuditedEntityDto<TPrimaryKey> : ExtensibleCreationAuditedEntityDto<TPrimaryKey>, IAuditedObject
{
/// <inheritdoc />
public DateTime? LastModificationTime { get; set; }
/// <inheritdoc />
public Guid? LastModifierId { get; set; }
}
/// <summary>
/// This class can be inherited by DTO classes to implement <see cref="IAuditedObject"/> interface.
/// It also implements the <see cref="IHasExtraProperties"/> interface.
/// </summary>
[Serializable]
public abstract class ExtensibleAuditedEntityDto : ExtensibleCreationAuditedEntityDto, IAuditedObject
{
/// <inheritdoc />
public DateTime? LastModificationTime { get; set; }
/// <inheritdoc />
public Guid? LastModifierId { get; set; }
}
}

40
framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleAuditedEntityWithUserDto.cs

@ -0,0 +1,40 @@
using System;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
namespace Volo.Abp.Application.Dtos
{
/// <summary>
/// This class can be inherited by DTO classes to implement <see cref="IAuditedObject"/> interface.
/// It has the <see cref="Creator"/> and <see cref="LastModifier"/> objects as a DTOs represent the related user.
/// It also implements the <see cref="IHasExtraProperties"/> interface.
/// </summary>
/// <typeparam name="TPrimaryKey">Type of primary key</typeparam>
/// <typeparam name="TUserDto">Type of the User DTO</typeparam>
[Serializable]
public abstract class ExtensibleAuditedEntityWithUserDto<TPrimaryKey, TUserDto> : ExtensibleAuditedEntityDto<TPrimaryKey>, IAuditedObject<TUserDto>
{
/// <inheritdoc />
public TUserDto Creator { get; set; }
/// <inheritdoc />
public TUserDto LastModifier { get; set; }
}
/// <summary>
/// This class can be inherited by DTO classes to implement <see cref="IAuditedObject"/> interface.
/// It has the <see cref="Creator"/> and <see cref="LastModifier"/> objects as a DTOs represent the related user.
/// It also implements the <see cref="IHasExtraProperties"/> interface.
/// </summary>
/// <typeparam name="TUserDto">Type of the User DTO</typeparam>
[Serializable]
public abstract class ExtensibleAuditedEntityWithUserDto<TUserDto> : ExtensibleAuditedEntityDto,
IAuditedObject<TUserDto>
{
/// <inheritdoc />
public TUserDto Creator { get; set; }
/// <inheritdoc />
public TUserDto LastModifier { get; set; }
}
}

35
framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleCreationAuditedEntityDto.cs

@ -0,0 +1,35 @@
using System;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
namespace Volo.Abp.Application.Dtos
{
/// <summary>
/// This class can be inherited by DTO classes to implement <see cref="ICreationAuditedObject"/> interface.
/// It also implements the <see cref="IHasExtraProperties"/> interface.
/// </summary>
/// <typeparam name="TPrimaryKey">Type of primary key</typeparam>
[Serializable]
public abstract class ExtensibleCreationAuditedEntityDto<TPrimaryKey> : ExtensibleEntityDto<TPrimaryKey>, ICreationAuditedObject
{
/// <inheritdoc />
public DateTime CreationTime { get; set; }
/// <inheritdoc />
public Guid? CreatorId { get; set; }
}
/// <summary>
/// This class can be inherited by DTO classes to implement <see cref="ICreationAuditedObject"/> interface.
/// It also implements the <see cref="IHasExtraProperties"/> interface.
/// </summary>
[Serializable]
public abstract class ExtensibleCreationAuditedEntityDto : ExtensibleEntityDto, ICreationAuditedObject
{
/// <inheritdoc />
public DateTime CreationTime { get; set; }
/// <inheritdoc />
public Guid? CreatorId { get; set; }
}
}

32
framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleCreationAuditedEntityWithUserDto.cs

@ -0,0 +1,32 @@
using System;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
namespace Volo.Abp.Application.Dtos
{
/// <summary>
/// This class can be inherited by DTO classes to implement <see cref="ICreationAuditedObject{TCreator}"/> interface.
/// It has the <see cref="Creator"/> object as a DTO represents the user.
/// It also implements the <see cref="IHasExtraProperties"/> interface.
/// </summary>
/// <typeparam name="TPrimaryKey">Type of primary key</typeparam>
/// <typeparam name="TUserDto">Type of the User DTO</typeparam>
[Serializable]
public abstract class ExtensibleCreationAuditedEntityWithUserDto<TPrimaryKey, TUserDto> : ExtensibleCreationAuditedEntityDto<TPrimaryKey>, ICreationAuditedObject<TUserDto>
{
public TUserDto Creator { get; set; }
}
/// <summary>
/// This class can be inherited by DTO classes to implement <see cref="ICreationAuditedObject{TCreator}"/> interface.
/// It has the <see cref="Creator"/> object as a DTO represents the user.
/// It also implements the <see cref="IHasExtraProperties"/> interface.
/// </summary>
/// <typeparam name="TUserDto">Type of the User DTO</typeparam>
[Serializable]
public abstract class ExtensibleCreationAuditedEntityWithUserDto<TUserDto> : ExtensibleCreationAuditedEntityDto,
ICreationAuditedObject<TUserDto>
{
public TUserDto Creator { get; set; }
}
}

28
framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleEntityDto.cs

@ -0,0 +1,28 @@
using System;
using Volo.Abp.ObjectExtending;
namespace Volo.Abp.Application.Dtos
{
[Serializable]
public abstract class ExtensibleEntityDto<TKey> : ExtensibleObject, IEntityDto<TKey>
{
/// <summary>
/// Id of the entity.
/// </summary>
public TKey Id { get; set; }
public override string ToString()
{
return $"[DTO: {GetType().Name}] Id = {Id}";
}
}
[Serializable]
public abstract class ExtensibleEntityDto : ExtensibleObject, IEntityDto
{
public override string ToString()
{
return $"[DTO: {GetType().Name}]";
}
}
}

41
framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleFullAuditedEntityDto.cs

@ -0,0 +1,41 @@
using System;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
namespace Volo.Abp.Application.Dtos
{
/// <summary>
/// This class can be inherited by DTO classes to implement <see cref="IFullAuditedObject"/> interface.
/// It also implements the <see cref="IHasExtraProperties"/> interface.
/// </summary>
/// <typeparam name="TPrimaryKey">Type of primary key</typeparam>
[Serializable]
public abstract class ExtensibleFullAuditedEntityDto<TPrimaryKey> : ExtensibleAuditedEntityDto<TPrimaryKey>, IFullAuditedObject
{
/// <inheritdoc />
public bool IsDeleted { get; set; }
/// <inheritdoc />
public Guid? DeleterId { get; set; }
/// <inheritdoc />
public DateTime? DeletionTime { get; set; }
}
/// <summary>
/// This class can be inherited by DTO classes to implement <see cref="IFullAuditedObject"/> interface.
/// It also implements the <see cref="IHasExtraProperties"/> interface.
/// </summary>
[Serializable]
public abstract class ExtensibleFullAuditedEntityDto : ExtensibleAuditedEntityDto, IFullAuditedObject
{
/// <inheritdoc />
public bool IsDeleted { get; set; }
/// <inheritdoc />
public Guid? DeleterId { get; set; }
/// <inheritdoc />
public DateTime? DeletionTime { get; set; }
}
}

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

Loading…
Cancel
Save