Browse Source

Merge remote-tracking branch 'abpframework/dev' into liangshiwei/backgrondjob-isenabled

pull/3382/head
liangshiwei 7 years ago
parent
commit
20474e2a1b
  1. 8
      build/common.ps1
  2. 1
      common.DotSettings
  3. 2
      common.props
  4. 3
      docs/cs/AspNetCore/Widgets.md
  5. 4
      docs/cs/Contribution/Localization-Text-Files.md
  6. 2
      docs/cs/Getting-Started-AspNetCore-Application.md
  7. 2
      docs/cs/Getting-Started-Console-Application.md
  8. 4
      docs/en/API/Dynamic-CSharp-API-Clients.md
  9. 51
      docs/en/Application-Services.md
  10. 30
      docs/en/Authorization.md
  11. 3
      docs/en/AutoMapper-Integration.md
  12. 43
      docs/en/Background-Jobs-Hangfire.md
  13. 26
      docs/en/Best-Practices/Application-Services.md
  14. 1
      docs/en/Best-Practices/Data-Transfer-Objects.md
  15. 4
      docs/en/Best-Practices/Entity-Framework-Core-Integration.md
  16. 4
      docs/en/Best-Practices/MongoDB-Integration.md
  17. 2
      docs/en/CLI.md
  18. 4
      docs/en/Contribution/Localization-Text-Files.md
  19. 13
      docs/en/Customizing-Application-Modules-Extending-Entities.md
  20. 99
      docs/en/Customizing-Application-Modules-Overriding-Services.md
  21. 2
      docs/en/Entities.md
  22. 19
      docs/en/Entity-Framework-Core-Migrations.md
  23. 41
      docs/en/Entity-Framework-Core.md
  24. 2
      docs/en/Getting-Started-AspNetCore-Application.md
  25. 2
      docs/en/Getting-Started-Console-Application.md
  26. 201
      docs/en/How-To/Azure-Active-Directory-Authentication-MVC.md
  27. 113
      docs/en/How-To/Customize-Login-Page-MVC.md
  28. 101
      docs/en/How-To/Customize-SignIn-Manager.md
  29. 9
      docs/en/How-To/Index.md
  30. 2
      docs/en/Modules/Docs.md
  31. 365
      docs/en/Object-Extensions.md
  32. 17
      docs/en/Object-To-Object-Mapping.md
  33. 177
      docs/en/Startup-Templates/Application.md
  34. 2
      docs/en/Tutorials/Part-2.md
  35. 77
      docs/en/UI/Angular/Component-Replacement.md
  36. 101
      docs/en/UI/Angular/Container-Strategy.md
  37. 78
      docs/en/UI/Angular/Content-Projection-Service.md
  38. 74
      docs/en/UI/Angular/Content-Security-Strategy.md
  39. 95
      docs/en/UI/Angular/Content-Strategy.md
  40. 117
      docs/en/UI/Angular/Context-Strategy.md
  41. 60
      docs/en/UI/Angular/Cross-Origin-Strategy.md
  42. 2
      docs/en/UI/Angular/Custom-Setting-Page.md
  43. 87
      docs/en/UI/Angular/Dom-Insertion-Service.md
  44. 90
      docs/en/UI/Angular/Dom-Strategy.md
  45. 213
      docs/en/UI/Angular/Lazy-Load-Service.md
  46. 1284
      docs/en/UI/Angular/Linked-List.md
  47. 110
      docs/en/UI/Angular/Loading-Strategy.md
  48. 200
      docs/en/UI/Angular/Projection-Strategy.md
  49. 6
      docs/en/UI/Angular/Service-Proxies.md
  50. 4
      docs/en/UI/Angular/Track-By-Service.md
  51. 4
      docs/en/UI/AspNetCore/Bundling-Minification.md
  52. 42
      docs/en/UI/AspNetCore/Tag-Helpers/Popovers.md
  53. 2
      docs/en/UI/AspNetCore/Widgets.md
  54. 1484
      docs/en/UI/Common/Utils/Linked-List.md
  55. 29
      docs/en/docs-nav.json
  56. BIN
      docs/en/images/angular-folder-structure.png
  57. BIN
      docs/en/images/angular-template-structure-diagram.png
  58. BIN
      docs/en/images/react-native-folder-structure.png
  59. BIN
      docs/en/images/react-native-navigation-structure.png
  60. BIN
      docs/en/images/react-native-store-folder.png
  61. 2
      docs/pt-BR/Tutorials/Angular/Part-I.md
  62. 2
      docs/pt-BR/Tutorials/Angular/Part-II.md
  63. 2
      docs/pt-BR/Tutorials/Angular/Part-III.md
  64. 2
      docs/pt-BR/Tutorials/AspNetCore-Mvc/Part-I.md
  65. 2
      docs/pt-BR/Tutorials/AspNetCore-Mvc/Part-II.md
  66. 2
      docs/pt-BR/Tutorials/AspNetCore-Mvc/Part-III.md
  67. 4
      docs/zh-Hans/API/Dynamic-CSharp-API-Clients.md
  68. 47
      docs/zh-Hans/Application-Services.md
  69. 30
      docs/zh-Hans/Authorization.md
  70. 3
      docs/zh-Hans/AutoMapper-Integration.md
  71. 43
      docs/zh-Hans/Background-Jobs-Hangfire.md
  72. 18
      docs/zh-Hans/Best-Practices/Application-Services.md
  73. 1
      docs/zh-Hans/Best-Practices/Data-Transfer-Objects.md
  74. 4
      docs/zh-Hans/Best-Practices/Entity-Framework-Core-Integration.md
  75. 6
      docs/zh-Hans/Best-Practices/MongoDB-Integration.md
  76. 2
      docs/zh-Hans/CLI.md
  77. 4
      docs/zh-Hans/Contribution/Localization-Text-Files.md
  78. 13
      docs/zh-Hans/Customizing-Application-Modules-Extending-Entities.md
  79. 99
      docs/zh-Hans/Customizing-Application-Modules-Overriding-Services.md
  80. 2
      docs/zh-Hans/Entities.md
  81. 15
      docs/zh-Hans/Entity-Framework-Core-Migrations.md
  82. 41
      docs/zh-Hans/Entity-Framework-Core.md
  83. 3
      docs/zh-Hans/Getting-Started-Angular-Template.md
  84. 2
      docs/zh-Hans/Getting-Started-AspNetCore-Application.md
  85. 2
      docs/zh-Hans/Getting-Started-Console-Application.md
  86. 6
      docs/zh-Hans/Getting-Started-With-Startup-Templates.md
  87. 198
      docs/zh-Hans/How-To/Azure-Active-Directory-Authentication-MVC.md
  88. 113
      docs/zh-Hans/How-To/Customize-Login-Page-MVC.md
  89. 101
      docs/zh-Hans/How-To/Customize-SignIn-Manager.md
  90. 9
      docs/zh-Hans/How-To/Index.md
  91. 267
      docs/zh-Hans/Object-Extensions.md
  92. 17
      docs/zh-Hans/Object-To-Object-Mapping.md
  93. 10
      docs/zh-Hans/Startup-Templates/Application.md
  94. 2
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-I.md
  95. 12
      docs/zh-Hans/UI/AspNetCore/Bundling-Minification.md
  96. 468
      docs/zh-Hans/UI/AspNetCore/Customization-User-Interface.md
  97. 3
      docs/zh-Hans/UI/AspNetCore/Libraries/DatatablesNet.md
  98. 1454
      docs/zh-Hans/UI/Common/Utils/Linked-List.md
  99. 26
      docs/zh-Hans/docs-nav.json
  100. BIN
      docs/zh-Hans/images/angular-folder-structure.png

8
build/common.ps1

@ -21,14 +21,6 @@ $solutionPaths = (
"../modules/client-simulation", "../modules/client-simulation",
"../templates/module/aspnet-core", "../templates/module/aspnet-core",
"../templates/app/aspnet-core", "../templates/app/aspnet-core",
"../samples/BasicAspNetCoreApplication",
"../samples/BasicConsoleApplication",
"../samples/BookStore",
"../samples/BookStore-Angular-MongoDb/aspnet-core",
"../samples/BookStore-Modular/modules/book-management",
"../samples/BookStore-Modular/application",
"../samples/DashboardDemo",
"../samples/MicroserviceDemo", "../samples/MicroserviceDemo",
"../samples/RabbitMqEventBus",
"../abp_io/AbpIoLocalization" "../abp_io/AbpIoLocalization"
) )

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>

2
common.props

@ -1,7 +1,7 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<Version>2.4.0</Version> <Version>2.6.0</Version>
<NoWarn>$(NoWarn);CS1591</NoWarn> <NoWarn>$(NoWarn);CS1591</NoWarn>
<PackageIconUrl>https://abp.io/assets/abp_nupkg.png</PackageIconUrl> <PackageIconUrl>https://abp.io/assets/abp_nupkg.png</PackageIconUrl>
<PackageProjectUrl>https://abp.io</PackageProjectUrl> <PackageProjectUrl>https://abp.io</PackageProjectUrl>

3
docs/cs/AspNetCore/Widgets.md

@ -502,4 +502,5 @@ Configure<AbpWidgetOptions>(options =>
## Podívejte se také na ## Podívejte se také na
* [Příklad projektu (zdrojový kód)](https://github.com/abpframework/abp/tree/dev/samples/DashboardDemo). * [Příklad projektu (zdrojový kód)](https://github.com/abpframework/abp-samples/tree/master/DashboardDemo).

4
docs/cs/Contribution/Localization-Text-Files.md

@ -29,8 +29,8 @@ Toto je seznam lokalizačních textových souborů pro každého kdo chce přisp
* https://github.com/abpframework/abp/tree/master/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Localization/Resources/AbpSettingManagement/en.json * https://github.com/abpframework/abp/tree/master/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Localization/Resources/AbpSettingManagement/en.json
* https://github.com/abpframework/abp/tree/master/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/Localization/ApplicationContracts/en.json * https://github.com/abpframework/abp/tree/master/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/Localization/ApplicationContracts/en.json
* https://github.com/abpframework/abp/tree/master/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Localization/Resources/AbpTenantManagement/Web/en.json * https://github.com/abpframework/abp/tree/master/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Localization/Resources/AbpTenantManagement/Web/en.json
* https://github.com/abpframework/abp/tree/master/samples/BookStore/src/Acme.BookStore.Domain.Shared/Localization/BookStore/en.json * https://github.com/abpframework/abp-samples/tree/master/BookStore/src/Acme.BookStore.Domain.Shared/Localization/BookStore/en.json
* https://github.com/abpframework/abp/tree/master/samples/DashboardDemo/src/DashboardDemo.Domain/Localization/DashboardDemo/en.json * https://github.com/abpframework/abp-samples/tree/master/DashboardDemo/src/DashboardDemo.Domain/Localization/DashboardDemo/en.json
* https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/en.json * https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/en.json
* https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/en.json * https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/en.json
* https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/en.json * https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/en.json

2
docs/cs/Getting-Started-AspNetCore-Application.md

@ -153,5 +153,5 @@ namespace BasicAspNetCoreApplication
## Zdrojový kód ## Zdrojový kód
Získejte zdrojový kód vzorového projektu vytvořeného v tomto tutoriálů [z tohoto odkazu](https://github.com/abpframework/abp/tree/master/samples/BasicAspNetCoreApplication). Získejte zdrojový kód vzorového projektu vytvořeného v tomto tutoriálů [z tohoto odkazu](https://github.com/abpframework/abp-samples/tree/master/BasicAspNetCoreApplication).

2
docs/cs/Getting-Started-Console-Application.md

@ -178,4 +178,4 @@ Stačí volat metodu `options.UseAutofac()` v možnostech `AbpApplicationFactory
## Zdrojový kód ## Zdrojový kód
Získejte zdrojový kód vzorového projektu vytvořeného v tomto tutoriálů [z tohoto odkazu](https://github.com/abpframework/abp/tree/master/samples/BasicConsoleApplication). Získejte zdrojový kód vzorového projektu vytvořeného v tomto tutoriálů [z tohoto odkazu](https://github.com/abpframework/abp-samples/tree/master/BasicConsoleApplication).

4
docs/en/API/Dynamic-CSharp-API-Clients.md

@ -69,7 +69,7 @@ public class MyClientAppModule : AbpModule
} }
```` ````
See the "RemoteServiceOptions" section below for more detailed configuration. See the "AbpRemoteServiceOptions" section below for more detailed configuration.
## Usage ## Usage
@ -104,7 +104,7 @@ While you can inject `IBookAppService` like above to use the client proxy, you c
## Configuration ## Configuration
### RemoteServiceOptions ### AbpRemoteServiceOptions
`AbpRemoteServiceOptions` is automatically set from the `appsettings.json` by default. Alternatively, you can use `Configure` method to set or override it. Example: `AbpRemoteServiceOptions` is automatically set from the `appsettings.json` by default. Alternatively, you can use `Configure` method to set or override it. Example:

51
docs/en/Application-Services.md

@ -132,6 +132,8 @@ The `CreateAsync` method above manually creates a `Book` entity from given `Crea
However, in many cases, it's very practical to use **auto object mapping** to set properties of an object from a similar object. ABP provides an [object to object mapping](Object-To-Object-Mapping.md) infrastructure to make this even easier. However, in many cases, it's very practical to use **auto object mapping** to set properties of an object from a similar object. ABP provides an [object to object mapping](Object-To-Object-Mapping.md) infrastructure to make this even easier.
Object to object mapping provides abstractions and it is implemented by the [AutoMapper](https://automapper.org/) library by default.
Let's create another method to get a book. First, define the method in the `IBookAppService` interface: Let's create another method to get a book. First, define the method in the `IBookAppService` interface:
````csharp ````csharp
@ -146,7 +148,6 @@ public interface IBookAppService : IApplicationService
`BookDto` is a simple [DTO](Data-Transfer-Objects.md) class defined as below: `BookDto` is a simple [DTO](Data-Transfer-Objects.md) class defined as below:
````csharp ````csharp
[AbpAutoMapFrom(typeof(Book))] //Defines the mapping
public class BookDto public class BookDto
{ {
public Guid Id { get; set; } public Guid Id { get; set; }
@ -159,21 +160,38 @@ public class BookDto
} }
```` ````
* `BookDto` defines `[AbpAutoMapFrom(typeof(Book))]` attribute to create the object mapping from `Book` to `BookDto`. AutoMapper requires to create a mapping [profile class](https://docs.automapper.org/en/stable/Configuration.html#profile-instances). Example:
Then you can implement the `GetAsync` method as shown below: ````csharp
public class MyProfile : Profile
{
public MyProfile()
{
CreateMap<Book, BookDto>();
}
}
````
You should then register profiles using the `AbpAutoMapperOptions`:
````csharp ````csharp
public async Task<BookDto> GetAsync(Guid id) [DependsOn(typeof(AbpAutoMapperModule))]
public class MyModule : AbpModule
{ {
var book = await _bookRepository.GetAsync(id); public override void ConfigureServices(ServiceConfigurationContext context)
return book.MapTo<BookDto>(); {
Configure<AbpAutoMapperOptions>(options =>
{
//Add all mappings defined in the assembly of the MyModule class
options.AddMaps<MyModule>();
});
}
} }
```` ````
`MapTo` extension method converts `Book` object to `BookDto` object by copying all properties with the same naming. `AddMaps` registers all profile classes defined in the assembly of the given class, typically your module class. It also registers for the [attribute mapping](https://docs.automapper.org/en/stable/Attribute-mapping.html).
An alternative to the `MapTo` is using the `IObjectMapper` service: Then you can implement the `GetAsync` method as shown below:
````csharp ````csharp
public async Task<BookDto> GetAsync(Guid id) public async Task<BookDto> GetAsync(Guid id)
@ -183,8 +201,6 @@ public async Task<BookDto> GetAsync(Guid id)
} }
```` ````
While the second syntax is a bit harder to write, it better works if you write unit tests.
See the [object to object mapping document](Object-To-Object-Mapping.md) for more. See the [object to object mapping document](Object-To-Object-Mapping.md) for more.
## Validation ## Validation
@ -250,7 +266,6 @@ public interface ICrudAppService<
DTO classes used in this example are `BookDto` and `CreateUpdateBookDto`: DTO classes used in this example are `BookDto` and `CreateUpdateBookDto`:
````csharp ````csharp
[AbpAutoMapFrom(typeof(Book))]
public class BookDto : AuditedEntityDto<Guid> public class BookDto : AuditedEntityDto<Guid>
{ {
public string Name { get; set; } public string Name { get; set; }
@ -260,7 +275,6 @@ public class BookDto : AuditedEntityDto<Guid>
public float Price { get; set; } public float Price { get; set; }
} }
[AbpAutoMapTo(typeof(Book))]
public class CreateUpdateBookDto public class CreateUpdateBookDto
{ {
[Required] [Required]
@ -275,6 +289,19 @@ public class CreateUpdateBookDto
} }
```` ````
[Profile](https://docs.automapper.org/en/stable/Configuration.html#profile-instances) class of DTO class.
```csharp
public class MyProfile : Profile
{
public MyProfile()
{
CreateMap<Book, BookDto>();
CreateMap<CreateUpdateBookDto, Book>();
}
}
```
* `CreateUpdateBookDto` is shared by create and update operations, but you could use separated DTO classes as well. * `CreateUpdateBookDto` is shared by create and update operations, but you could use separated DTO classes as well.
And finally, the `BookAppService` implementation is very simple: And finally, the `BookAppService` implementation is very simple:

30
docs/en/Authorization.md

@ -142,6 +142,20 @@ myGroup.AddPermission(
); );
``` ```
#### Enable/Disable Permissions
A permission is enabled by default. It is possible to disable a permission. A disabled permission will be prohibited for everyone. You can still check for the permission, but it will always return prohibited.
Example definition:
````csharp
myGroup.AddPermission("Author_Management", isEnabled: false);
````
You normally don't need to define a disabled permission (unless you temporary want disable a feature of your application). However, you may want to disable a permission defined in a depended module. In this way you can disable the related application functionality. See the "*Changing Permission Definitions of a Depended Module*" section below for an example usage.
> Note: Checking an undefined permission will throw an exception while a disabled permission check simply returns prohibited (false).
#### Child Permissions #### Child Permissions
A permission may have child permissions. It is especially useful when you want to create a hierarchical permission tree where a permission may have additional sub permissions which are available only if the parent permission has been granted. A permission may have child permissions. It is especially useful when you want to create a hierarchical permission tree where a permission may have additional sub permissions which are available only if the parent permission has been granted.
@ -208,6 +222,18 @@ See [policy based authorization](https://docs.microsoft.com/en-us/aspnet/core/se
A class deriving from the `PermissionDefinitionProvider` (just like the example above) can also get existing permission definitions (defined by the depended [modules](Module-Development-Basics.md)) and change their definitions. A class deriving from the `PermissionDefinitionProvider` (just like the example above) can also get existing permission definitions (defined by the depended [modules](Module-Development-Basics.md)) and change their definitions.
Example:
````csharp
context
.GetPermissionOrNull(IdentityPermissions.Roles.Delete)
.IsEnabled = false;
````
When you write this code inside your permission definition provider, it finds the "role deletion" permission of the [Identity Module](Modules/Identity.md) and disabled the permission, so no one can delete a role on the application.
> Tip: It is better to check the value returned by the `GetPermissionOrNull` method since it may return null if the given permission was not defined.
## IAuthorizationService ## IAuthorizationService
ASP.NET Core provides the `IAuthorizationService` that can be used to check for authorization. Once you inject, you can use it in your code to conditionally control the authorization. ASP.NET Core provides the `IAuthorizationService` that can be used to check for authorization. Once you inject, you can use it in your code to conditionally control the authorization.
@ -339,10 +365,10 @@ A permission value provider should return one of the following values from the `
- `PermissionGrantResult.Prohibited` is returned to prohibit the user for the permission. If any of the providers return `Prohibited`, the result will always be `Prohibited`. Doesn't matter what other providers return. - `PermissionGrantResult.Prohibited` is returned to prohibit the user for the permission. If any of the providers return `Prohibited`, the result will always be `Prohibited`. Doesn't matter what other providers return.
- `PermissionGrantResult.Undefined` is returned if this value provider could not decide about the permission value. Return this to let other providers check the permission. - `PermissionGrantResult.Undefined` is returned if this value provider could not decide about the permission value. Return this to let other providers check the permission.
Once a provider is defined, it should be added to the `PermissionOptions` as shown below: Once a provider is defined, it should be added to the `AbpPermissionOptions` as shown below:
```csharp ```csharp
Configure<PermissionOptions>(options => Configure<AbpPermissionOptions>(options =>
{ {
options.ValueProviders.Add<SystemAdminPermissionValueProvider>(); options.ValueProviders.Add<SystemAdminPermissionValueProvider>();
}); });

3
docs/en/AutoMapper-Integration.md

@ -1,3 +0,0 @@
## AutoMapper Integration
TODO

43
docs/en/Background-Jobs-Hangfire.md

@ -40,4 +40,45 @@ public class YourModule : AbpModule
## Configuration ## Configuration
TODO... You can install any storage for Hangfire. The most common one is SQL Server (see the [Hangfire.SqlServer](https://www.nuget.org/packages/Hangfire.SqlServer) NuGet package).
After you have installed these NuGet packages, you need to configure your project to use Hangfire.
1.First, we change the `Module` class (example: `<YourProjectName>HttpApiHostModule`) to add Hangfire configuration of the storage and connection string in the `ConfigureServices` method:
````csharp
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
var hostingEnvironment = context.Services.GetHostingEnvironment();
//... other configarations.
ConfigureHangfire(context, configuration);
}
private void ConfigureHangfire(ServiceConfigurationContext context, IConfiguration configuration)
{
context.Services.AddHangfire(config =>
{
config.UseSqlServerStorage(configuration.GetConnectionString("Default"));
});
}
````
2. We need to add `UseHangfireServer` call in the `OnApplicationInitialization` method in `Module` class
If you want to use hangfire's dashboard, you can add it, too: by `UseHangfireDashboard`
````csharp
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
// ... others
app.UseHangfireServer();
app.UseHangfireDashboard();
}
````

26
docs/en/Best-Practices/Application-Services.md

@ -17,17 +17,18 @@
##### Basic DTO ##### Basic DTO
**Do** define a **basic** DTO for an entity. **Do** define a **basic** DTO for an aggregate root.
- Include all the **primitive properties** directly on the entity. - Include all the **primitive properties** directly on the aggregate root.
- Exception: Can **exclude** properties for **security** reasons (like User.Password). - Exception: Can **exclude** properties for **security** reasons (like `User.Password`).
- Include all the **sub collections** of the entity where every item in the collection is a simple **relation DTO**. - Include all the **sub collections** of the entity where every item in the collection is a simple **relation DTO**.
- Inherit from one of the **extensible entity DTO** classes for aggregate roots (and entities implement the `IHasExtraProperties`).
Example: Example:
```c# ```c#
[Serializable] [Serializable]
public class IssueDto : FullAuditedEntityDto<Guid> public class IssueDto : ExtensibleFullAuditedEntityDto<Guid>
{ {
public string Title { get; set; } public string Title { get; set; }
public string Text { get; set; } public string Text { get; set; }
@ -57,7 +58,7 @@ Example:
````C# ````C#
[Serializable] [Serializable]
public class IssueWithDetailsDto : FullAuditedEntityDto<Guid> public class IssueWithDetailsDto : ExtensibleFullAuditedEntityDto<Guid>
{ {
public string Title { get; set; } public string Title { get; set; }
public string Text { get; set; } public string Text { get; set; }
@ -66,14 +67,14 @@ public class IssueWithDetailsDto : FullAuditedEntityDto<Guid>
} }
[Serializable] [Serializable]
public class MilestoneDto : EntityDto<Guid> public class MilestoneDto : ExtensibleEntityDto<Guid>
{ {
public string Name { get; set; } public string Name { get; set; }
public bool IsClosed { get; set; } public bool IsClosed { get; set; }
} }
[Serializable] [Serializable]
public class LabelDto : EntityDto<Guid> public class LabelDto : ExtensibleEntityDto<Guid>
{ {
public string Name { get; set; } public string Name { get; set; }
public string Color { get; set; } public string Color { get; set; }
@ -120,6 +121,7 @@ Task<List<QuestionWithDetailsDto>> GetListAsync(QuestionListQueryDto queryDto);
* **Do** use the `CreateAsync` **method name**. * **Do** use the `CreateAsync` **method name**.
* **Do** get a **specialized input** DTO to create the entity. * **Do** get a **specialized input** DTO to create the entity.
* **Do** inherit the DTO class from the `ExtensibleObject` (or any other class implements the `IHasExtraProperties`) to allow to pass extra properties if needed.
* **Do** use **data annotations** for input validation. * **Do** use **data annotations** for input validation.
* Share constants between domain wherever possible (via constants defined in the **domain shared** package). * Share constants between domain wherever possible (via constants defined in the **domain shared** package).
* **Do** return **the detailed** DTO for new created entity. * **Do** return **the detailed** DTO for new created entity.
@ -135,10 +137,11 @@ The related **DTO**:
````C# ````C#
[Serializable] [Serializable]
public class CreateQuestionDto public class CreateQuestionDto : ExtensibleObject
{ {
[Required] [Required]
[StringLength(QuestionConsts.MaxTitleLength, MinimumLength = QuestionConsts.MinTitleLength)] [StringLength(QuestionConsts.MaxTitleLength,
MinimumLength = QuestionConsts.MinTitleLength)]
public string Title { get; set; } public string Title { get; set; }
[StringLength(QuestionConsts.MaxTextLength)] [StringLength(QuestionConsts.MaxTextLength)]
@ -152,6 +155,7 @@ public class CreateQuestionDto
- **Do** use the `UpdateAsync` **method name**. - **Do** use the `UpdateAsync` **method name**.
- **Do** get a **specialized input** DTO to update the entity. - **Do** get a **specialized input** DTO to update the entity.
- **Do** inherit the DTO class from the `ExtensibleObject` (or any other class implements the `IHasExtraProperties`) to allow to pass extra properties if needed.
- **Do** get the Id of the entity as a separated primitive parameter. Do not include to the update DTO. - **Do** get the Id of the entity as a separated primitive parameter. Do not include to the update DTO.
- **Do** use **data annotations** for input validation. - **Do** use **data annotations** for input validation.
- Share constants between domain wherever possible (via constants defined in the **domain shared** package). - Share constants between domain wherever possible (via constants defined in the **domain shared** package).
@ -200,6 +204,10 @@ This method votes a question and returns the current score of the question.
* **Do not** use LINQ/SQL for querying data from database inside the application service methods. It's repository's responsibility to perform LINQ/SQL queries from the data source. * **Do not** use LINQ/SQL for querying data from database inside the application service methods. It's repository's responsibility to perform LINQ/SQL queries from the data source.
#### Extra Properties
* **Do** use either `MapExtraPropertiesTo` extension method ([see](Object-Extensions.md)) or configure the object mapper (`MapExtraProperties`) to allow application developers to be able to extend the objects and services.
#### Manipulating / Deleting Entities #### Manipulating / Deleting Entities
* **Do** always get all the related entities from repositories to perform the operations on them. * **Do** always get all the related entities from repositories to perform the operations on them.

1
docs/en/Best-Practices/Data-Transfer-Objects.md

@ -2,6 +2,7 @@
* **Do** define DTOs in the **application contracts** package. * **Do** define DTOs in the **application contracts** package.
* **Do** inherit from the pre-built **base DTO classes** where possible and necessary (like `EntityDto<TKey>`, `CreationAuditedEntityDto<TKey>`, `AuditedEntityDto<TKey>`, `FullAuditedEntityDto<TKey>` and so on). * **Do** inherit from the pre-built **base DTO classes** where possible and necessary (like `EntityDto<TKey>`, `CreationAuditedEntityDto<TKey>`, `AuditedEntityDto<TKey>`, `FullAuditedEntityDto<TKey>` and so on).
* **Do** inherit from the **extensible DTO** classes for the **aggregate roots** (like `ExtensibleAuditedEntityDto<TKey>`), because aggregate roots are extensible objects and extra properties are mapped to DTOs in this way.
* **Do** define DTO members with **public getter and setter**. * **Do** define DTO members with **public getter and setter**.
* **Do** use **data annotations** for **validation** on the properties of DTOs those are inputs of the service. * **Do** use **data annotations** for **validation** on the properties of DTOs those are inputs of the service.
* **Do** not add any **logic** into DTOs except implementing `IValidatableObject` when necessary. * **Do** not add any **logic** into DTOs except implementing `IValidatableObject` when necessary.

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

@ -107,10 +107,10 @@ public static class IdentityDbContextModelBuilderExtensions
```` ````
* **Do** call `b.ConfigureByConvention();` for each entity mapping (as shown above). * **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 `AbpModelBuilderConfigurationOptions`. Example:
````C# ````C#
public class IdentityModelBuilderConfigurationOptions : ModelBuilderConfigurationOptions public class IdentityModelBuilderConfigurationOptions : AbpModelBuilderConfigurationOptions
{ {
public IdentityModelBuilderConfigurationOptions() public IdentityModelBuilderConfigurationOptions()
: base(AbpIdentityConsts.DefaultDbTablePrefix, AbpIdentityConsts.DefaultDbSchema) : base(AbpIdentityConsts.DefaultDbTablePrefix, AbpIdentityConsts.DefaultDbSchema)

4
docs/en/Best-Practices/MongoDB-Integration.md

@ -90,11 +90,11 @@ public static class AbpIdentityMongoDbContextExtensions
} }
``` ```
- **Do** create a **configuration options** class by inheriting from the `MongoModelBuilderConfigurationOptions`. Example: - **Do** create a **configuration options** class by inheriting from the `AbpMongoModelBuilderConfigurationOptions`. Example:
```c# ```c#
public class IdentityMongoModelBuilderConfigurationOptions public class IdentityMongoModelBuilderConfigurationOptions
: MongoModelBuilderConfigurationOptions : AbpMongoModelBuilderConfigurationOptions
{ {
public IdentityMongoModelBuilderConfigurationOptions() public IdentityMongoModelBuilderConfigurationOptions()
: base(AbpIdentityConsts.DefaultDbTablePrefix) : base(AbpIdentityConsts.DefaultDbTablePrefix)

2
docs/en/CLI.md

@ -60,6 +60,8 @@ abp new Acme.BookStore
* `--version` or `-v`: Specifies the ABP & template version. It can be a [release tag](https://github.com/abpframework/abp/releases) or a [branch name](https://github.com/abpframework/abp/branches). Uses the latest release if not specified. Most of the times, you will want to use the latest version. * `--version` or `-v`: Specifies the ABP & template version. It can be a [release tag](https://github.com/abpframework/abp/releases) or a [branch name](https://github.com/abpframework/abp/branches). Uses the latest release if not specified. Most of the times, you will want to use the latest version.
* `--template-source` or `-ts`: Specifies a custom template source to use to build the project. Local and network sources can be used(Like `D\localTemplate` or `https://<your url>.zip`). * `--template-source` or `-ts`: Specifies a custom template source to use to build the project. Local and network sources can be used(Like `D\localTemplate` or `https://<your url>.zip`).
* `--create-solution-folder` or `-csf`: Specifies if the project will be in a new folder in the output folder or directly the output folder. * `--create-solution-folder` or `-csf`: Specifies if the project will be in a new folder in the output folder or directly the output folder.
* `--connection-string` or `-cs`: Overwrites the default connection strings in all `appsettings.json` files. The default connection string is `Server=localhost;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true`. You can set your own connection string if you don't want to use the default. Be aware that the default database provider is `SQL Server`, therefore you can only enter connection string for SQL Server!
* `--local-framework-ref --abp-path`: keeps local references to projects instead of replacing with NuGet package references.
### add-package ### add-package

4
docs/en/Contribution/Localization-Text-Files.md

@ -23,8 +23,8 @@ Here, a list of localization text files for anyone wants to contribute to locali
* https://github.com/abpframework/abp/tree/master/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json * https://github.com/abpframework/abp/tree/master/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json
* https://github.com/abpframework/abp/tree/master/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/en.json * https://github.com/abpframework/abp/tree/master/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/en.json
* https://github.com/abpframework/abp/tree/master/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en.json * https://github.com/abpframework/abp/tree/master/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en.json
* https://github.com/abpframework/abp/tree/master/samples/BookStore/src/Acme.BookStore.Domain.Shared/Localization/BookStore/en.json * https://github.com/abpframework/abp-samples/tree/master/BookStore/src/Acme.BookStore.Domain.Shared/Localization/BookStore/en.json
* https://github.com/abpframework/abp/tree/master/samples/DashboardDemo/src/DashboardDemo.Domain.Shared/Localization/DashboardDemo/en.json * https://github.com/abpframework/abp-samples/tree/master/DashboardDemo/src/DashboardDemo.Domain.Shared/Localization/DashboardDemo/en.json
* https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/en.json * https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/en.json
* https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/en.json * https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/en.json
* https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/en.json * https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/en.json

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

@ -37,13 +37,14 @@ As mentioned above, all extra properties of an entity are stored as a single JSO
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. 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 `EntityExtensionManager` static class: 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 ````csharp
EntityExtensionManager.AddProperty<IdentityUser, string>( ObjectExtensionManager.Instance
"SocialSecurityNumber", .MapEfCoreProperty<IdentityUser, string>(
b => { b.HasMaxLength(32); } "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 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).
@ -57,8 +58,6 @@ You can then use the same extra properties system defined in the previous sectio
## Creating a New Entity Maps to the Same Database Table/Collection ## Creating a New Entity Maps to the Same Database Table/Collection
While using the extra properties approach is **easy to use** and suitable for some scenarios, it has some drawbacks described in the [entities document](Entities.md).
Another approach can be **creating your own entity** mapped to **the same database table** (or collection for a MongoDB database). Another approach can be **creating your own entity** mapped to **the same database table** (or collection for a MongoDB database).
`AppUser` entity in the [application startup template](Startup-Templates/Application.md) already implements this approach. [EF Core Migrations document](Entity-Framework-Core-Migrations.md) describes how to implement it and manage **EF Core database migrations** in such a case. It is also possible for MongoDB, while this time you won't deal with the database migration problems. `AppUser` entity in the [application startup template](Startup-Templates/Application.md) already implements this approach. [EF Core Migrations document](Entity-Framework-Core-Migrations.md) describes how to implement it and manage **EF Core database migrations** in such a case. It is also possible for MongoDB, while this time you won't deal with the database migration problems.

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

@ -161,6 +161,105 @@ Check the [localization system](Localization.md) to learn how to localize the er
Overriding controllers, framework services, view component classes and any other type of classes registered to dependency injection can be overridden just like the examples above. Overriding controllers, framework services, view component classes and any other type of classes registered to dependency injection can be overridden just like the examples above.
## Extending Data Transfer Objects
**Extending [entities](Entities.md)** is possible as described in the [Extending Entities document](Customizing-Application-Modules-Extending-Entities.md). In this way, you can add **custom properties** to entities and perform **additional business logic** by overriding the related services as described above.
It is also possible to extend Data Transfer Objects (**DTOs**) used by the application services. In this way, you can get extra properties from the UI (or client) and return extra properties from the service.
### Example
Assuming that you've already added a `SocialSecurityNumber` as described in the [Extending Entities document](Customizing-Application-Modules-Extending-Entities.md) and want to include this information while getting the list of users from the `GetListAsync` method of the `IdentityUserAppService`.
You can use the [object extension system](Object-Extensions.md) to add the property to the `IdentityUserDto`. Write this code inside the `YourProjectNameDtoExtensions` class comes with the application startup template:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUserDto, string>(
"SocialSecurityNumber"
);
````
This code defines a `SocialSecurityNumber` to the `IdentityUserDto` class as a `string` type. That's all. Now, if you call the `/api/identity/users` HTTP API (which uses the `IdentityUserAppService` internally) from a REST API client, you will see the `SocialSecurityNumber` value in the `extraProperties` section.
````json
{
"totalCount": 1,
"items": [{
"tenantId": null,
"userName": "admin",
"name": "admin",
"surname": null,
"email": "admin@abp.io",
"emailConfirmed": false,
"phoneNumber": null,
"phoneNumberConfirmed": false,
"twoFactorEnabled": false,
"lockoutEnabled": true,
"lockoutEnd": null,
"concurrencyStamp": "b4c371a0ab604de28af472fa79c3b70c",
"isDeleted": false,
"deleterId": null,
"deletionTime": null,
"lastModificationTime": "2020-04-09T21:25:47.0740706",
"lastModifierId": null,
"creationTime": "2020-04-09T21:25:46.8308744",
"creatorId": null,
"id": "8edecb8f-1894-a9b1-833b-39f4725db2a3",
"extraProperties": {
"SocialSecurityNumber": "123456789"
}
}]
}
````
Manually added the `123456789` value to the database for now.
All pre-built modules support extra properties in their DTOs, so you can configure easily.
### Definition Check
When you [define](Customizing-Application-Modules-Extending-Entities.md) an extra property for an entity, it doesn't automatically appear in all the related DTOs, because of the security. The extra property may contain a sensitive data and you may not want to expose it to the clients by default.
So, you need to explicitly define the same property for the corresponding DTO if you want to make it available for the DTO (as just done above). If you want to allow to set it on user creation, you also need to define it for the `IdentityUserCreateDto`.
If the property is not so secure, this can be tedious. Object extension system allows you to ignore this definition check for a desired property. See the example below:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, string>(
"SocialSecurityNumber",
options =>
{
options.MapEfCore(b => b.HasMaxLength(32));
options.CheckPairDefinitionOnMapping = false;
}
);
````
This is another approach to define a property for an entity (`ObjectExtensionManager` has more, see [its document](Object-Extensions.md)). This time, we set `CheckPairDefinitionOnMapping` to false to skip definition check while mapping entities to DTOs and vice verse.
If you don't like this approach but want to add a single property to multiple objects (DTOs) easier, `AddOrUpdateProperty` can get an array of types to add the extra property:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<string>(
new[]
{
typeof(IdentityUserDto),
typeof(IdentityUserCreateDto),
typeof(IdentityUserUpdateDto)
},
"SocialSecurityNumber"
);
````
### About the User Interface
This system allows you to add extra properties to entities and DTOs and execute custom business code, however it does nothing related to the User Interface.
See [Overriding the User Interface](Customizing-Application-Modules-Overriding-User-Interface.md) guide for the UI part.
## How to Find the Services? ## How to Find the Services?
[Module documents](Modules/Index.md) includes the list of the major services they define. In addition, you can investigate [their source code](https://github.com/abpframework/abp/tree/dev/modules) to explore all the services. [Module documents](Modules/Index.md) includes the list of the major services they define. In addition, you can investigate [their source code](https://github.com/abpframework/abp/tree/dev/modules) to explore all the services.

2
docs/en/Entities.md

@ -375,7 +375,7 @@ The way to store this dictionary in the database depends on the database provide
* For [Entity Framework Core](Entity-Framework-Core.md), here are two type of configurations; * 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. * 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 `EntityExtensionManager` to define a separate table field for a desired extra property. Properties those are not configured through the `EntityExtensionManager` 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 `EntityExtensionManager`. * 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

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

@ -6,7 +6,7 @@ This document begins by **introducing the default structure** provided by [the a
### Source Code ### Source Code
You can find the source code of the example project referenced by this document [here](https://github.com/abpframework/abp/tree/dev/samples/EfCoreMigrationDemo). However, you need to read and understand this document in order to understand the example project's source code. You can find the source code of the example project referenced by this document [here](https://github.com/abpframework/abp-samples/tree/master/EfCoreMigrationDemo). However, you need to read and understand this document in order to understand the example project's source code.
## About the EF Core Code First Migrations ## About the EF Core Code First Migrations
@ -409,18 +409,19 @@ public static class MyProjectNameEntityExtensions
{ {
OneTimeRunner.Run(() => OneTimeRunner.Run(() =>
{ {
EntityExtensionManager.AddProperty<IdentityRole, string>( ObjectExtensionManager.Instance
"Title", .MapEfCoreProperty<IdentityRole, string>(
b => { b.HasMaxLength(128); } "Title",
); builder => { builder.HasMaxLength(64); }
);
}); });
} }
} }
```` ````
> Instead of hard-coded "Title" string, we suggest to use `nameof(AppRole.Title)`. > Instead of hard-coded "Title" string, we suggest to use `nameof(AppRole.Title)` or use a constant string.
`EntityExtensionManager` is used to add properties to existing entities. Since `EntityExtensionManager` is static, we should call it once. `OneTimeRunner` is a simple utility class defined by the ABP Framework. `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.
See the [EF Core integration documentation](Entity-Framework-Core.md) for more about the entity extension system. See the [EF Core integration documentation](Entity-Framework-Core.md) for more about the entity extension system.
@ -543,7 +544,7 @@ In this way, you can easily attach any type of value to an entity of a depended
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. 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 `EntityExtensionManager` 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. 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
@ -881,4 +882,4 @@ This document explains how to split your databases and manage your database migr
## Source Code ## Source Code
You can find the source code of the example project referenced by this document [here](https://github.com/abpframework/abp/tree/dev/samples/EfCoreMigrationDemo). However, you need to read and understand this document in order to understand the example project's source code. You can find the source code of the example project referenced by this document [here](https://github.com/abpframework/abp-samples/tree/master/EfCoreMigrationDemo). However, you need to read and understand this document in order to understand the example project's source code.

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

@ -298,56 +298,57 @@ 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.
## Extra Properties & Entity Extension Manager ## 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. 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. Entity extension system allows you to to store desired extra properties in separate fields in the related database table. By default, all the extra properties of an entity are stored as a single `JSON` object in the database.
For more information about the extra properties & the entity extension system, see the following documents: 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) * [Customizing the Application Modules: Extending Entities](Customizing-Application-Modules-Extending-Entities.md)
* [Entities](Entities.md) * [Entities](Entities.md)
This section only explains the `EntityExtensionManager` and its usage. This section only explains the EF Core related usage of the `ObjectExtensionManager`.
### AddProperty Method ### ObjectExtensionManager.Instance
`AddProperty` method of the `EntityExtensionManager` allows you to define additional properties for an entity type. `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: **Example**: Add `Title` property (database field) to the `IdentityRole` entity:
````csharp ````csharp
EntityExtensionManager.AddProperty<IdentityRole, string>( ObjectExtensionManager.Instance
"Title", .MapEfCoreProperty<IdentityRole, string>(
b => { b.HasMaxLength(128); } "Title",
); builder => { builder.HasMaxLength(64); }
);
```` ````
If the related module has implemented this feature (by using the `ConfigureExtensions` 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. 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.
>`AddProperty` 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. >`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.
### ConfigureExtensions ### ConfigureEfCoreEntity
If you are building a reusable module and want to allow application developers to add properties to your entities, you can use the `ConfigureExtensions` extension method in your entity mapping: 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 ````csharp
builder.Entity<YourEntity>(b => builder.Entity<YourEntity>(b =>
{ {
b.ConfigureExtensions(); b.ConfigureObjectExtensions();
//... //...
}); });
```` ````
If you call `ConfigureByConvention()` extension method (like `b.ConfigureByConvention()` in this example), ABP Framework internally calls the `ConfigureExtensions` method. It is a **best practice** to use the `ConfigureByConvention()` method since it also configures database mapping for base properties by convention. > 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. See the "*ConfigureByConvention Method*" section above for more information.
### GetPropertyNames
`EntityExtensionManager.GetPropertyNames` static method can be used the names of the extension properties defined for this entity. It is normally not needed by an application code, but used by the ABP Framework internally.
## Advanced Topics ## Advanced Topics
### Set Default Repository Classes ### Set Default Repository Classes

2
docs/en/Getting-Started-AspNetCore-Application.md

@ -153,5 +153,5 @@ namespace BasicAspNetCoreApplication
## Source Code ## Source Code
Get source code of the sample project created in this tutorial from [here](https://github.com/abpframework/abp/tree/master/samples/BasicAspNetCoreApplication). Get source code of the sample project created in this tutorial from [here](https://github.com/abpframework/abp-samples/tree/master/BasicAspNetCoreApplication).

2
docs/en/Getting-Started-Console-Application.md

@ -178,4 +178,4 @@ Just called `options.UseAutofac()` method in the `AbpApplicationFactory.Create`
## Source Code ## Source Code
Get source code of the sample project created in this tutorial from [here](https://github.com/abpframework/abp/tree/master/samples/BasicConsoleApplication). Get source code of the sample project created in this tutorial from [here](https://github.com/abpframework/abp-samples/tree/master/BasicConsoleApplication).

201
docs/en/How-To/Azure-Active-Directory-Authentication-MVC.md

@ -0,0 +1,201 @@
# How to Use the Azure Active Directory Authentication for MVC / Razor Page Applications
This guide demonstrates how to integrate AzureAD to an ABP application that enables users to sign in using OAuth 2.0 with credentials from **Azure Active Directory**.
Adding Azure Active Directory is pretty straightforward in ABP framework. Couple of configurations needs to be done correctly.
Two different **alternative approaches** for AzureAD integration will be demonstrated for better coverage.
1. **AddAzureAD**: This approach uses Microsoft [AzureAD UI nuget package](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.AzureAD.UI/) which is very popular when users search the web about how to integrate AzureAD to their web application.
2. **AddOpenIdConnect**: This approach uses default [OpenIdConnect](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.OpenIdConnect/) which can be used for not only AzureAD but for all OpenId connections.
> There is **no difference** in functionality between these approaches. AddAzureAD is an abstracted way of OpenIdConnection ([source](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADAuthenticationBuilderExtensions.cs#L122)) with predefined cookie settings.
>
> However there are key differences in integration to ABP applications because of default configurated signin schemes which will be explained below.
## 1. AddAzureAD
This approach uses the most common way to integrate AzureAD by using the [Microsoft AzureAD UI nuget package](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.AzureAD.UI/).
If you choose this approach, you will need to install `Microsoft.AspNetCore.Authentication.AzureAD.UI` package to your **.Web** project. Also, since AddAzureAD extension uses [configuration binding](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.1#default-configuration), you need to update your appsettings.json file located in your **.Web** project.
#### **Updating `appsettings.json`**
You need to add a new section to your `appsettings.json` which will be binded to configuration when configuring the `OpenIdConnectOptions`:
````json
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "<your-tenant-id>",
"ClientId": "<your-client-id>",
"Domain": "domain.onmicrosoft.com",
"CallbackPath": "/signin-azuread-oidc"
}
````
> Important configuration here is the CallbackPath. This value must be the same with one of your Azure AD-> app registrations-> Authentication -> RedirectUri.
Then, you need to configure the `OpenIdConnectOptions` to complete the integration.
#### Configuring OpenIdConnectOptions
In your **.Web** project, locate your **ApplicationWebModule** and modify `ConfigureAuthentication` method with the following:
````csharp
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier);
context.Services.AddAuthentication()
.AddIdentityServerAuthentication(options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = false;
options.ApiName = "Acme.BookStore";
})
.AddAzureAD(options => configuration.Bind("AzureAd", options));
context.Services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.Authority = options.Authority + "/v2.0/";
options.ClientId = configuration["AzureAd:ClientId"];
options.CallbackPath = configuration["AzureAd:CallbackPath"];
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters.ValidateIssuer = false;
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
options.SignInScheme = IdentityConstants.ExternalScheme;
options.Scope.Add("email");
});
}
````
> **Don't forget to:**
>
> * Add `.AddAzureAD(options => configuration.Bind("AzureAd", options))` after `.AddAuthentication()`. This binds your AzureAD appsettings and easy to miss out.
> * Add `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear()`. This will disable the default Microsoft claim type mapping.
> * Add `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier)`. Mapping this to [ClaimTypes.NameIdentifier](https://github.com/dotnet/runtime/blob/6d395de48ac718a913e567ae80961050f2a9a4fa/src/libraries/System.Security.Claims/src/System/Security/Claims/ClaimTypes.cs#L59) is important since default SignIn Manager behavior uses this claim type for external login information.
> * Add `options.SignInScheme = IdentityConstants.ExternalScheme` since [default signin scheme is `AzureADOpenID`](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADOpenIdConnectOptionsConfiguration.cs#L35).
> * Add `options.Scope.Add("email")` if you are using **v2.0** endpoint of AzureAD since v2.0 endpoint doesn't return the `email` claim as default. The [Account Module](../Modules/Account.md) uses `email` claim to [register external users](https://github.com/abpframework/abp/blob/be32a55449e270d2d456df3dabdc91f3ffdd4fa9/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs#L215).
You are done and integration is completed.
## 2. Alternative Approach: AddOpenIdConnect
If you don't want to use an extra nuget package in your application, you can use the straight default [OpenIdConnect](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.OpenIdConnect/) which can be used for all OpenId connections including AzureAD external authentication.
You don't have to use `appsettings.json` configuration but it is a good practice to set AzureAD information in the `appsettings.json`.
To get the AzureAD information from `appsettings.json`, which will be used in `OpenIdConnectOptions` configuration, simply add a new section to `appsettings.json` located in your **.Web** project:
````json
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "<your-tenant-id>",
"ClientId": "<your-client-id>",
"Domain": "domain.onmicrosoft.com",
"CallbackPath": "/signin-azuread-oidc"
}
````
Then, In your **.Web** project; you can modify the `ConfigureAuthentication` method located in your **ApplicationWebModule** with the following:
````csharp
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier);
context.Services.AddAuthentication()
.AddIdentityServerAuthentication(options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = false;
options.ApiName = "BookStore";
})
.AddOpenIdConnect("AzureOpenId", "Azure Active Directory OpenId", options =>
{
options.Authority = "https://login.microsoftonline.com/" + configuration["AzureAd:TenantId"] + "/v2.0/";
options.ClientId = configuration["AzureAd:ClientId"];
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.CallbackPath = configuration["AzureAd:CallbackPath"];
options.RequireHttpsMetadata = false;
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("email");
});
}
````
And that's it, integration is completed. Keep on mind that you can connect any other external authentication providers.
## The Source Code
You can find the source code of the completed example [here](https://github.com/abpframework/abp-samples/tree/master/aspnet-core/Authentication-Customization).
# FAQ
* Help! `GetExternalLoginInfoAsync` returns `null`!
* There can be 2 reasons for this;
1. You are trying to authenticate against wrong scheme. Check if you set **SignInScheme** to `IdentityConstants.ExternalScheme`:
````csharp
options.SignInScheme = IdentityConstants.ExternalScheme;
````
2. Your `ClaimTypes.NameIdentifier` is `null`. Check if you added claim mapping:
````csharp
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier);
````
* Help! I keep getting ***AADSTS50011: The reply URL specified in the request does not match the reply URLs configured for the application*** error!
* If you set your **CallbackPath** in appsettings as:
````csharp
"AzureAd": {
...
"CallbackPath": "/signin-azuread-oidc"
}
````
your **Redirect URI** of your application in azure portal must be with <u>domain</u> like `https://localhost:44320/signin-azuread-oidc`, not only `/signin-azuread-oidc`.
* Help! I am getting ***System.ArgumentNullException: Value cannot be null. (Parameter 'userName')*** error!
* This occurs when you use Azure Authority **v2.0 endpoint** without requesting `email` scope. [Abp checks unique email to create user](https://github.com/abpframework/abp/blob/037ef9abe024c03c1f89ab6c933710bcfe3f5c93/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs#L208). Simply add
````csharp
options.Scope.Add("email");
````
to your openid configuration.
* How can I **debug/watch** which claims I get before they get mapped?
* You can add a simple event under openid configuration to debug before mapping like:
````csharp
options.Events.OnTokenValidated = (async context =>
{
var claimsFromOidcProvider = context.Principal.Claims.ToList();
await Task.CompletedTask;
});
````
## See Also
* [How to Customize the Login Page for MVC / Razor Page Applications](Customize-Login-Page-MVC.md).
* [How to Customize the SignIn Manager for ABP Applications](Customize-SignIn-Manager.md).

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

@ -0,0 +1,113 @@
# How to Customize the Login Page for MVC / Razor Page Applications
When you create a new application using the [application startup template](../Startup-Templates/Application.md), source code of the login page will not be inside your solution, so you can not directly change it. The login page comes from the [Account Module](../Modules/Account.md) that is used a [NuGet package](https://www.nuget.org/packages/Volo.Abp.Account.Web) reference.
This document explains how to customize the login page for your own application.
## Create a Login PageModel
Create a new class inheriting from the [LoginModel](https://github.com/abpframework/abp/blob/037ef9abe024c03c1f89ab6c933710bcfe3f5c93/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs) of the Account module.
````csharp
public class CustomLoginModel : LoginModel
{
public CustomLoginModel(
Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider schemeProvider,
Microsoft.Extensions.Options.IOptions<Volo.Abp.Account.Web.AbpAccountOptions> accountOptions)
: base(schemeProvider, accountOptions)
{
}
}
````
> Naming convention is important here. If your class name doesn't end with `LoginModel`, you need to manually replace the `LoginModel` using the [dependency injection](../Dependency-Injection.md) system.
Then you can override any method you need and add new methods and properties needed by the UI.
## Overriding the Login Page UI
Create folder named **Account** under **Pages** directory and create a **Login.cshtml** under this folder. It will automatically override the `Login.cshtml` file defined in the Account Module thanks to the [Virtual File System](../Virtual-File-System.md).
A good way to customize a page is to copy its source code. [Click here](https://github.com/abpframework/abp/blob/dev/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml) for the source code of the login page. At the time this document has been written, the source code was like below:
````xml
@page
@using Volo.Abp.Account.Settings
@using Volo.Abp.Settings
@model Acme.BookStore.Web.Pages.Account.CustomLoginModel
@inherits Volo.Abp.Account.Web.Pages.Account.AccountPage
@inject Volo.Abp.Settings.ISettingProvider SettingProvider
@if (Model.EnableLocalLogin)
{
<div class="card mt-3 shadow-sm rounded">
<div class="card-body p-5">
<h4>@L["Login"]</h4>
@if (await SettingProvider.IsTrueAsync(AccountSettingNames.IsSelfRegistrationEnabled))
{
<strong>
@L["AreYouANewUser"]
<a href="@Url.Page("./Register", new {returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash})" class="text-decoration-none">@L["Register"]</a>
</strong>
}
<form method="post" class="mt-4">
<input asp-for="ReturnUrl" />
<input asp-for="ReturnUrlHash" />
<div class="form-group">
<label asp-for="LoginInput.UserNameOrEmailAddress"></label>
<input asp-for="LoginInput.UserNameOrEmailAddress" class="form-control" />
<span asp-validation-for="LoginInput.UserNameOrEmailAddress" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="LoginInput.Password"></label>
<input asp-for="LoginInput.Password" class="form-control" />
<span asp-validation-for="LoginInput.Password" class="text-danger"></span>
</div>
<div class="form-check">
<label asp-for="LoginInput.RememberMe" class="form-check-label">
<input asp-for="LoginInput.RememberMe" class="form-check-input" />
@Html.DisplayNameFor(m => m.LoginInput.RememberMe)
</label>
</div>
<abp-button type="submit" button-type="Primary" name="Action" value="Login" class="btn-block btn-lg mt-3">@L["Login"]</abp-button>
</form>
</div>
<div class="card-footer text-center border-0">
<abp-button type="button" button-type="Link" name="Action" value="Cancel" class="px-2 py-0">@L["Cancel"]</abp-button> @* TODO: Only show if identity server is used *@
</div>
</div>
}
@if (Model.VisibleExternalProviders.Any())
{
<div class="col-md-6">
<h4>@L["UseAnotherServiceToLogIn"]</h4>
<form asp-page="./Login" asp-page-handler="ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" asp-route-returnUrlHash="@Model.ReturnUrlHash" method="post">
<input asp-for="ReturnUrl" />
<input asp-for="ReturnUrlHash" />
@foreach (var provider in Model.VisibleExternalProviders)
{
<button type="submit" class="btn btn-primary" name="provider" value="@provider.AuthenticationScheme" title="@L["GivenTenantIsNotAvailable", provider.DisplayName]">@provider.DisplayName</button>
}
</form>
</div>
}
@if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders.Any())
{
<div class="alert alert-warning">
<strong>@L["InvalidLoginRequest"]</strong>
@L["ThereAreNoLoginSchemesConfiguredForThisClient"]
</div>
}
````
Just changed the `@model` to `Acme.BookStore.Web.Pages.Account.CustomLoginModel` to use the customized `PageModel` class. You can change it however your application needs.
## The Source Code
You can find the source code of the completed example [here](https://github.com/abpframework/abp-samples/tree/master/aspnet-core/Authentication-Customization).
## See Also
* [ASP.NET Core (MVC / Razor Pages) User Interface Customization Guide](../UI/AspNetCore/Customization-User-Interface.md).

101
docs/en/How-To/Customize-SignIn-Manager.md

@ -0,0 +1,101 @@
# How to Customize the SignIn Manager for ABP Applications
After creating a new application using the [application startup template](../Startup-Templates/Application.md), you may want extend or change the default behavior of the SignIn Manager for your authentication and registration flow needs. ABP [Account Module](../Modules/Account.md) uses the [Identity Management Module](../Modules/Identity.md) for SignIn Manager and the [Identity Management Module](../Modules/Identity.md) uses default [Microsoft Identity SignIn Manager](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs) ([see here](https://github.com/abpframework/abp/blob/be32a55449e270d2d456df3dabdc91f3ffdd4fa9/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreModule.cs#L17)).
To write your Custom SignIn Manager, you need to extend [Microsoft Identity SignIn Manager](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs) class and register it to the DI container.
This document explains how to customize the SignIn Manager for your own application.
## Create a CustomSignInManager
Create a new class inheriting the [SignInMager](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs) of Microsoft Identity package.
````csharp
public class CustomSignInManager : Microsoft.AspNetCore.Identity.SignInManager<Volo.Abp.Identity.IdentityUser>
{
public CustomSignInManager(
Microsoft.AspNetCore.Identity.UserManager<Volo.Abp.Identity.IdentityUser> userManager,
Microsoft.AspNetCore.Http.IHttpContextAccessor contextAccessor,
Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory<Volo.Abp.Identity.IdentityUser> claimsFactory,
Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Identity.IdentityOptions> optionsAccessor,
Microsoft.Extensions.Logging.ILogger<Microsoft.AspNetCore.Identity.SignInManager<Volo.Abp.Identity.IdentityUser>> logger,
Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider schemes,
Microsoft.AspNetCore.Identity.IUserConfirmation<Volo.Abp.Identity.IdentityUser> confirmation)
: base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation)
{
}
}
````
> It is important to use **Volo.Abp.Identity.IdentityUser** type for SignInManager to inherit, not the AppUser of your application.
Afterwards you can override any of the SignIn Manager methods you need and add new methods and properties needed for your authentication or registration flow.
## Overriding the GetExternalLoginInfoAsync Method
In this case we'll be overriding the `GetExternalLoginInfoAsync` method which is invoked when a third party authentication is implemented.
A good way to override a method is copying its [source code](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Identity/Core/src/SignInManager.cs#L638-L674). In this case, we will be using a minorly modified version of the source code which explicitly shows the namespaces of the methods and properties to help better understanding of the concept.
````csharp
public override async Task<Microsoft.AspNetCore.Identity.ExternalLoginInfo> GetExternalLoginInfoAsync(string expectedXsrf = null)
{
var auth = await Context.AuthenticateAsync(Microsoft.AspNetCore.Identity.IdentityConstants.ExternalScheme);
var items = auth?.Properties?.Items;
if (auth?.Principal == null || items == null || !items.ContainsKey("LoginProviderKey"))
{
return null;
}
if (expectedXsrf != null)
{
if (!items.ContainsKey("XsrfKey"))
{
return null;
}
var userId = items[XsrfKey] as string;
if (userId != expectedXsrf)
{
return null;
}
}
var providerKey = auth.Principal.FindFirstValue(ClaimTypes.NameIdentifier);
var provider = items[LoginProviderKey] as string;
if (providerKey == null || provider == null)
{
return null;
}
var providerDisplayName = (await GetExternalAuthenticationSchemesAsync()).FirstOrDefault(p => p.Name == provider)?.DisplayName
?? provider;
return new Microsoft.AspNetCore.Identity.ExternalLoginInfo(auth.Principal, provider, providerKey, providerDisplayName)
{
AuthenticationTokens = auth.Properties.GetTokens()
};
}
````
To get your overridden method invoked and your customized SignIn Manager class to work, you need to register your class to the [Dependency Injection System](../Dependency-Injection.md).
## Register to Dependency Injection
Registering `CustomSignInManager` should be done with adding **AddSignInManager** extension method of the [IdentityBuilderExtensions](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/IdentityBuilderExtensions.cs) of the [IdentityBuilder](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Extensions.Core/src/IdentityBuilder.cs).
Inside your `.Web` project, locate the `YourProjectNameWebModule` and add the following code under the `PreConfigureServices` method to replace the old `SignInManager` with your customized one:
````csharp
PreConfigure<IdentityBuilder>(identityBuilder =>
{
identityBuilder.AddSignInManager<CustomSignInManager>();
});
````
## The Source Code
You can find the source code of the completed example [here](https://github.com/abpframework/abp-samples/tree/master/aspnet-core/Authentication-Customization).
## See Also
* [How to Customize the Login Page for MVC / Razor Page Applications](Customize-Login-Page-MVC.md).
* [Identity Management Module](../Modules/Identity.md).

9
docs/en/How-To/Index.md

@ -0,0 +1,9 @@
# "How To" Guides
This section contains "how to" guides for some specific questions frequently asked. While some of them are common development tasks and not directly related to the ABP Framework, we think it is useful to have some concrete examples those directly work with your ABP based applications.
## Authentication
* [How to Customize the Login Page for MVC / Razor Page Applications](Customize-Login-Page-MVC.md)
* [How to Use the Azure Active Directory Authentication for MVC / Razor Page Applications](Azure-Active-Directory-Authentication-MVC.md)
* [How to Customize the SignIn Manager for ABP Applications](Customize-SignIn-Manager.md)

2
docs/en/Modules/Docs.md

@ -134,7 +134,7 @@ An ABP module must declare `[DependsOn]` attribute if it has a dependency upon a
{ {
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)
{ {
Configure<PermissionOptions>(options => Configure<AbpPermissionOptions>(options =>
{ {
options.DefinitionProviders.Add<MyProjectPermissionDefinitionProvider>(); options.DefinitionProviders.Add<MyProjectPermissionDefinitionProvider>();
}); });

365
docs/en/Object-Extensions.md

@ -0,0 +1,365 @@
# Object Extensions
ABP Framework provides an **object extension system** to allow you to **add extra properties** to an existing object **without modifying** the related class. This allows to extend functionalities implemented by a depended [application module](Modules/Index.md), especially when you want to [extend entities](Customizing-Application-Modules-Extending-Entities.md) and [DTOs](Customizing-Application-Modules-Overriding-Services.md) defined by the module.
> Object extension system is not normally not needed for your own objects since you can easily add regular properties to your own classes.
## IHasExtraProperties Interface
This is the interface to make a class extensible. It simply defines a `Dictionary` property:
````csharp
Dictionary<string, object> ExtraProperties { get; }
````
Then you can add or get extra properties using this dictionary.
### Base Classes
`IHasExtraProperties` interface is implemented by several base classes by default:
* Implemented by the `AggregateRoot` class (see [entities](Entities.md)).
* Implemented by `ExtensibleEntityDto`, `ExtensibleAuditedEntityDto`... base [DTO](Data-Transfer-Objects.md) classes.
* Implemented by the `ExtensibleObject`, which is a simple base class can be inherited for any type of object.
So, if you inherit from these classes, your class will also be extensible. If not, you can always implement it manually.
### Fundamental Extension Methods
While you can directly use the `ExtraProperties` property of a class, it is suggested to use the following extension methods while working with the extra properties.
#### SetProperty
Used to set the value of an extra property:
````csharp
user.SetProperty("Title", "My Title");
user.SetProperty("IsSuperUser", true);
````
`SetProperty` returns the same object, so you can chain it:
````csharp
user.SetProperty("Title", "My Title")
.SetProperty("IsSuperUser", true);
````
#### GetProperty
Used to read the value of an extra property:
````csharp
var title = user.GetProperty<string>("Title");
if (user.GetProperty<bool>("IsSuperUser"))
{
//...
}
````
* `GetProperty` is a generic method and takes the object type as the generic parameter.
* Returns the default value if given property was not set before (default value is `0` for `int`, `false` for `bool`... etc).
##### Non Primitive Property Types
If your property type is not a primitive (int, bool, enum, string... etc) type, then you need to use non-generic version of the `GetProperty` which returns an `object`.
#### HasProperty
Used to check if the object has a property set before.
#### RemoveProperty
Used to remove a property from the object. Use this methods instead of setting a `null` value for the property.
### Some Best Practices
Using magic strings for the property names is dangerous since you can easily type the property name wrong - it is not type safe. Instead;
* Define a constant for your extra property names
* Create extension methods to easily set your extra properties.
Example:
````csharp
public static class IdentityUserExtensions
{
private const string TitlePropertyName = "Title";
public static void SetTitle(this IdentityUser user, string title)
{
user.SetProperty(TitlePropertyName, title);
}
public static string GetTitle(this IdentityUser user)
{
return user.GetProperty<string>(TitlePropertyName);
}
}
````
Then you can easily set or get the `Title` property:
````csharp
user.SetTitle("My Title");
var title = user.GetTitle();
````
## Object Extension Manager
While you can set arbitrary properties to an extensible object (which implements the `IHasExtraProperties` interface), `ObjectExtensionManager` is used to explicitly define extra properties for extensible classes.
Explicitly defining an extra property has some use cases:
* Allows to control how the extra property is handled on object to object mapping (see the section below).
* Allows to define metadata for the property. For example, you can map an extra property to a table field in the database while using the [EF Core](Entity-Framework-Core.md).
> `ObjectExtensionManager` implements the singleton pattern (`ObjectExtensionManager.Instance`) and you should define object extensions before your application startup. The [application startup template](Startup-Templates/Application.md) has some pre-defined static classes to safely define object extensions inside.
### AddOrUpdate
`AddOrUpdate` is the main method to define a extra properties or update extra properties for an object.
Example: Define extra properties for the `IdentityUser` entity:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdate<IdentityUser>(options =>
{
options.AddOrUpdateProperty<string>("SocialSecurityNumber");
options.AddOrUpdateProperty<bool>("IsSuperUser");
}
);
````
### AddOrUpdateProperty
While `AddOrUpdateProperty` can be used on the `options` as shown before, if you want to define a single extra property, you can use the shortcut extension method too:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, string>("SocialSecurityNumber");
````
Sometimes it would be practical to define a single extra property to multiple types. Instead of defining one by one, you can use the following code:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<string>(
new[]
{
typeof(IdentityUserDto),
typeof(IdentityUserCreateDto),
typeof(IdentityUserUpdateDto)
},
"SocialSecurityNumber"
);
````
### Property Configuration
`AddOrUpdateProperty` can also get an action that can perform additional configuration on the property definition:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, string>(
"SocialSecurityNumber",
options =>
{
//Configure options...
});
````
> `options` has a dictionary, named `Configuration` which makes the object extension definitions even extensible. It is used by the EF Core to map extra properties to table fields in the database. See the [extending entities](Customizing-Application-Modules-Extending-Entities.md) document.
The following sections explain the fundamental property configuration options.
#### CheckPairDefinitionOnMapping
Controls how to check property definitions while mapping two extensible objects. See the "Object to Object Mapping" section to understand the `CheckPairDefinitionOnMapping` option better.
## Validation
You may want to add some **validation rules** for the extra properties you've defined. `AddOrUpdateProperty` method options allows two ways of performing validation:
1. You can add **data annotation attributes** for a property.
2. You can write an action (code block) to perform a **custom validation**.
Validation works when you use the object in a method that is **automatically validated** (e.g. controller actions, page handler methods, application service methods...). So, all extra properties are validated whenever the extended object is being validated.
### Data Annotation Attributes
All of the standard data annotation attributes are valid for extra properties. Example:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUserCreateDto, string>(
"SocialSecurityNumber",
options =>
{
options.ValidationAttributes.Add(new RequiredAttribute());
options.ValidationAttributes.Add(
new StringLengthAttribute(32) {
MinimumLength = 6
}
);
});
````
With this configuration, `IdentityUserCreateDto` objects will be invalid without a valid `SocialSecurityNumber` value provided.
### Custom Validation
If you need, you can add a custom action that is executed to validate the extra properties. Example:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUserCreateDto, string>(
"SocialSecurityNumber",
options =>
{
options.Validators.Add(context =>
{
var socialSecurityNumber = context.Value as string;
if (socialSecurityNumber == null ||
socialSecurityNumber.StartsWith("X"))
{
context.ValidationErrors.Add(
new ValidationResult(
"Invalid social security number: " + socialSecurityNumber,
new[] { "SocialSecurityNumber" }
)
);
}
});
});
````
`context.ServiceProvider` can be used to resolve a service dependency for advanced scenarios.
In addition to add custom validation logic for a single property, you can add a custom validation logic that is executed in object level. Example:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdate<IdentityUserCreateDto>(objConfig =>
{
//Define two properties with their own validation rules
objConfig.AddOrUpdateProperty<string>("Password", propertyConfig =>
{
propertyConfig.ValidationAttributes.Add(new RequiredAttribute());
});
objConfig.AddOrUpdateProperty<string>("PasswordRepeat", propertyConfig =>
{
propertyConfig.ValidationAttributes.Add(new RequiredAttribute());
});
//Write a common validation logic works on multiple properties
objConfig.Validators.Add(context =>
{
if (context.ValidatingObject.GetProperty<string>("Password") !=
context.ValidatingObject.GetProperty<string>("PasswordRepeat"))
{
context.ValidationErrors.Add(
new ValidationResult(
"Please repeat the same password!",
new[] { "Password", "PasswordRepeat" }
)
);
}
});
});
````
## Object to Object Mapping
Assume that you've added an extra property to an extensible entity object and used auto [object to object mapping](Object-To-Object-Mapping.md) to map this entity to an extensible DTO class. You need to be careful in such a case, because the extra property may contain a **sensitive data** that should not be available to clients.
This section offers some **good practices** to control your extra properties on object mapping.
### MapExtraPropertiesTo
`MapExtraPropertiesTo` is an extension method provided by the ABP Framework to copy extra properties from an object to another in a controlled manner. Example usage:
````csharp
identityUser.MapExtraPropertiesTo(identityUserDto);
````
`MapExtraPropertiesTo` **requires to define properties** (as described above) in **both sides** (`IdentityUser` and `IdentityUserDto` in this case) in order to copy the value to the target object. Otherwise, it doesn't copy the value even if it does exists in the source object (`identityUser` in this example). There are some ways to overload this restriction.
#### MappingPropertyDefinitionChecks
`MapExtraPropertiesTo` gets an additional parameter to control the definition check for a single mapping operation:
````csharp
identityUser.MapExtraPropertiesTo(
identityUserDto,
MappingPropertyDefinitionChecks.None
);
````
> Be careful since `MappingPropertyDefinitionChecks.None` copies all extra properties without any check. `MappingPropertyDefinitionChecks` enum has other members too.
If you want to completely disable definition check for a property, you can do it while defining the extra property (or update an existing definition) as shown below:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, string>(
"SocialSecurityNumber",
options =>
{
options.CheckPairDefinitionOnMapping = false;
});
````
#### Ignored Properties
You may want to ignore some properties on a specific mapping operation:
````csharp
identityUser.MapExtraPropertiesTo(
identityUserDto,
ignoredProperties: new[] {"MySensitiveProp"}
);
````
Ignored properties are not copied to the target object.
#### AutoMapper Integration
If you're using the [AutoMapper](https://automapper.org/) library, the ABP Framework also provides an extension method to utilize the `MapExtraPropertiesTo` method defined above.
You can use the `MapExtraProperties()` method inside your mapping profile.
````csharp
public class MyProfile : Profile
{
public MyProfile()
{
CreateMap<IdentityUser, IdentityUserDto>()
.MapExtraProperties();
}
}
````
It has the same parameters with the `MapExtraPropertiesTo` method.
## Entity Framework Core Database Mapping
If you're using the EF Core, you can map an extra property to a table field in the database. Example:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, string>(
"SocialSecurityNumber",
options =>
{
options.MapEfCore(b => b.HasMaxLength(32));
}
);
````
See the [Entity Framework Core Integration document](Entity-Framework-Core.md) for more.

17
docs/en/Object-To-Object-Mapping.md

@ -145,6 +145,23 @@ options.AddProfile<MyProfile>(validate: true);
> If you have multiple profiles and need to enable validation only for a few of them, first use `AddMaps` without validation, then use `AddProfile` for each profile you want to validate. > If you have multiple profiles and need to enable validation only for a few of them, first use `AddMaps` without validation, then use `AddProfile` for each profile you want to validate.
### Mapping the Object Extensions
[Object extension system](Object-Extensions.md) allows to define extra properties for existing classes. ABP Framework provides a mapping definition extension to properly map extra properties of two objects.
````csharp
public class MyProfile : Profile
{
public MyProfile()
{
CreateMap<User, UserDto>()
.MapExtraProperties();
}
}
````
It is suggested to use the `MapExtraProperties()` method if both classes are extensible objects (implement the `IHasExtraProperties` interface). See the [object extension document](Object-Extensions.md) for more.
## Advanced Topics ## Advanced Topics
### IObjectMapper<TContext> Interface ### IObjectMapper<TContext> Interface

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

@ -261,14 +261,181 @@ You should run the application with the given order:
### Angular UI ### Angular UI
If you choose Angular as the UI framework (using the `-u angular` option), the solution is separated into two folders: If you choose `Angular` as the UI framework (using the `-u angular` option), the solution is being separated into three folders:
* `angular` folder contains the Angular UI solution, the client side. * `angular` folder contains the Angular UI application, the client-side code.
* `aspnet-core` folder contains the ASP.NET Core solution, the server side. * `aspnet-core` folder contains the ASP.NET Core solution, the server-side code.
* `react-native` folder contains the React Native UI application, the client-side code for mobile.
Server side is very similar to the solution described above. `.HttpApi.Host` project serves the API, so the Angular application can consume it. The server-side is similar to the solution described above. `*.HttpApi.Host` project serves the API, so the `Angular` application consumes it.
The files under the `angular/src/environments` folder has the essential configuration of the application. Angular application folder structure looks like below:
![angular-folder-structure](../images/angular-folder-structure.png)
Each of ABP Commercial modules is an NPM package. Some ABP modules are added as a dependency in `package.json`. These modules install with their dependencies. To see all ABP packages, you can run the following command in the `angular` folder:
```bash
yarn list --pattern abp
```
Angular application module structure:
![Angular template structure diagram](../images/angular-template-structure-diagram.png)
#### AppModule
`AppModule` is the root module of the application. Some of ABP modules and some essential modules imported to the `AppModule`.
ABP Config modules also have imported to `AppModule`  for initially requirements of lazy-loadable ABP modules.
#### AppRoutingModule
There are lazy-loadable ABP modules in the `AppRoutingModule` as routes.
> Paths of ABP Modules should not be changed.
You should add `routes` property in the `data` object to add a link on the menu to redirect to your custom pages.
```js
{
path: 'dashboard',
loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule),
canActivate: [AuthGuard, PermissionGuard],
data: {
routes: {
name: 'ProjectName::Menu:Dashboard',
order: 2,
iconClass: 'fa fa-dashboard',
requiredPolicy: 'ProjectName.Dashboard.Host'
} as ABP.Route
}
}
```
In the above example;
* If the user is not logged in, AuthGuard blocks access and redirects to the login page.
* PermissionGuard checks the user's permission with `requiredPolicy` property of the `rotues` object. If the user is not authorized to access the page, the 403 page appears.
* `name` property of `routes` is the menu link label. A localization key can be defined .
* `iconClass` property of `routes` object is the menu link icon class.
* `requiredPolicy` property of `routes` object is the required policy key to access the page.
After the above `routes` definition, if the user is authorized, the dashboard link will appear on the menu.
#### Shared Module
The modules that may be required for all modules have imported to the `SharedModule`. You should import the `SharedModule` to all modules.
See the [Sharing Modules](https://angular.io/guide/sharing-ngmodules) document.
#### Environments
The files under the `src/environments` folder has the essential configuration of the application.
#### Home Module
Home module is an example lazy-loadable module that loads on the root address of the application.
#### Styles
The required style files added to `styles` array in the `angular.json`. `AppComponent` loads some style files lazily via `LazyLoadService` after the main bundle is loaded to shorten the first rendering time.
#### Testing
You should create your tests in the same folder as the file file you want to test.
See the [testing document](https://angular.io/guide/testing).
#### Depended Packages
* [NG Bootstrap](https://ng-bootstrap.github.io/) is used as UI component library.
* [NGXS](https://www.ngxs.io/) is used as state management library.
* [angular-oauth2-oidc](https://github.com/manfredsteyer/angular-oauth2-oidc) is used to support for OAuth 2 and OpenId Connect (OIDC).
* [Chart.js](https://www.chartjs.org/) is used to create widgets.
* [ngx-validate](https://github.com/ng-turkey/ngx-validate) is used for dynamic validation of reactive forms.
### React Native
The solution includes the [React Native](https://reactnative.dev/) application in the `react-native` folder as default.
The server-side is similar to the solution described above. `*.HttpApi.Host` project serves the API, so the React Native application consumes it.
The React Native application was generated with [Expo](https://expo.io/). Expo is a set of tools built around React Native to help you quickly start an app and, while it has many features.
React Native application folder structure as like below:
![react-native-folder-structure](../images/react-native-folder-structure.png)
* `App.js` is bootstrap component of the application.
* `Environment.js` file has the essential configuration of the application. `prod` and `dev` configurations defined in this file.
* [Contexts](https://reactjs.org/docs/context.html) are created in the `src/contexts` folder.
* [Higher order components](https://reactjs.org/docs/higher-order-components.html) are created in the`src/hocs` folder.
* [Custom hooks](https://reactjs.org/docs/hooks-custom.html#extracting-a-custom-hook) are created in the`src/hooks`.
* [Axios interceptors](https://github.com/axios/axios#interceptors) are created in the `src/interceptors` folder.
* Utility functions are exported from `src/utils` folder.
#### Components
Components that can be used on all screens are created in the `src/components` folder. All components have created as a function that able to use [hooks](https://reactjs.org/docs/hooks-intro.html).
#### Screens
![react-native-navigation-structure](../images/react-native-navigation-structure.png)
Screens are created by creating folders that separate their names in the `src/screens` folder. Certain parts of some screens can be split into components.
Each screen is used in a navigator in the `src/navigators` folder.
#### Navigation
[React Navigation](https://reactnavigation.org/) is used as a navigation library. Navigators are created in the `src/navigators`. A [drawer](https://reactnavigation.org/docs/drawer-based-navigation/) navigator and several [stack](https://reactnavigation.org/docs/hello-react-navigation/#installing-the-stack-navigator-library) navigators have created in this folder. See the [above diagram](#screens) for navigation structure.
#### State Management
[Redux](https://redux.js.org/) is used as state management library. [Redux Toolkit](https://redux-toolkit.js.org/) library is used as a toolset for efficient Redux development.
Actions, reducers, sagas, selectors are created in the `src/store` folder. Store folder as like below:
![react-native-store-folder](../images/react-native-store-folder.png)
* [**Store**](https://redux.js.org/basics/store) is defined in the `src/store/index.js` file.
* [**Actions**](https://redux.js.org/basics/actions/) are payloads of information that send data from your application to your store.
* [**Reducers**](https://redux.js.org/basics/reducers) specify how the application's state changes in response to actions sent to the store.
* [**Redux-Saga**](https://redux-saga.js.org/) is a library that aims to make application side effects (i.e. asynchronous things like data fetching and impure things like accessing the browser cache) easier to manage. Sagas are created in the `src/store/sagas` folder.
* [**Reselect**](https://github.com/reduxjs/reselect) library is used to create memoized selectors. Selectors are created in the `src/store/selectors` folder.
#### APIs
[Axios](https://github.com/axios/axios) is used as an HTTP client library. An Axios instance has exported from `src/api/API.js` file to make HTTP calls with the same config. `src/api` folder also has the API files that have been created for API calls.
#### Theming
[Native Base](https://nativebase.io/) is used as UI components library. Native Base components can customize easily. See the [Native Base customize](https://docs.nativebase.io/Customize.html#Customize) documentation. We followed the same way.
* Native Base theme variables are in the `src/theme/variables` folder.
* Native Base component styles are in the `src/theme/components` folder. These files have been generated with Native Base's `ejectTheme` script.
* Styles of components override with the files under the `src/theme/overrides` folder.
#### Testing
Unit tests will be created.
See the [Testing Overview](https://reactjs.org/docs/testing.html) document.
#### Depended Libraries
* [Native Base](https://nativebase.io/) is used as UI components library.
* [React Navigation](https://reactnavigation.org/) is used as navigation library.
* [Axios](https://github.com/axios/axios) is used as HTTP client library.
* [Redux](https://redux.js.org/) is used as state management library.
* [Redux Toolkit](https://redux-toolkit.js.org/) library is used as a toolset for efficient Redux development.
* [Redux-Saga](https://redux-saga.js.org/) is used to manage asynchronous processes.
* [Redux Persist](https://github.com/rt2zz/redux-persist) is used as state persistance.
* [Reselect](https://github.com/reduxjs/reselect) is used to create memoized selectors.
* [i18n-js](https://github.com/fnando/i18n-js) is used as i18n library.
* [expo-font](https://docs.expo.io/versions/latest/sdk/font/) library allows loading fonts easily.
* [Formik](https://github.com/jaredpalmer/formik) is used to build forms.
* [Yup](https://github.com/jquense/yup) is used for form validations.
## What's Next? ## What's Next?

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

@ -688,7 +688,7 @@ Open `book-list.component.html` file in `books\book-list` folder and replace the
* `abp-modal` is a pre-built component to show modals. While you could use another approach to show a modal, `abp-modal` provides additional benefits. * `abp-modal` is a pre-built component to show modals. While you could use another approach to show a modal, `abp-modal` provides additional benefits.
* We added `New book` button to the `AbpContentToolbar`. * We added `New book` button to the `AbpContentToolbar`.
Open `book-list.component.` file in `books\book-list` folder and replace the content as below: Open `book-list.component.ts` file in `books\book-list` folder and replace the content as below:
```js ```js
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';

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

@ -1,25 +1,28 @@
# 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`.
Then, open the `app.component.ts` and dispatch the `AddReplaceableComponent` action to replace your component with an ABP component as shown below: Then, open the `app.component.ts` and dispatch the `AddReplaceableComponent` action to replace your component with an ABP component as shown below:
```js ```js
import { ..., AddReplaceableComponent } from '@abp/ng.core'; import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent action
import { eIdentityComponents } from '@abp/ng.identity'; // imported eIdentityComponents enum
import { Store } from '@ngxs/store'; // imported Store
//...
export class AppComponent { export class AppComponent {
constructor(..., private store: Store) {} constructor(..., private store: Store) {} // injected Store
ngOnInit() { ngOnInit() {
this.store.dispatch( this.store.dispatch(
new AddReplaceableComponent({ new AddReplaceableComponent({
component: YourNewRoleComponent, component: YourNewRoleComponent,
key: 'Identity.RolesComponent', key: eIdentityComponents.Roles,
}), }),
); );
//... //...
@ -29,23 +32,53 @@ export class AppComponent {
![Example Usage](./images/component-replacement.gif) ![Example Usage](./images/component-replacement.gif)
## Available Replaceable Components
### How to Replace a Layout
| Component key | Description |
| -------------------------------------------------- | --------------------------------------------- | Each ABP theme module has 3 layouts named `ApplicationLayoutComponent`, `AccountLayoutComponent`, `EmptyLayoutComponent`. These layouts can be replaced with the same way.
| Account.LoginComponent | Login page |
| Account.RegisterComponent | Register page | > A layout component template should contain `<router-outlet></router-outlet>` element.
| Account.ManageProfileComponent | Manage Profile page |
| Account.AuthWrapperComponent | This component wraps register and login pages | The below example describes how to replace the `ApplicationLayoutComponent`:
| Account.ChangePasswordComponent | Change password form |
| Account.PersonalSettingsComponent | Personal settings form | Run the following command to generate a layout in `angular` folder:
| Account.TenantBoxComponentInputs | Tenant changing box |
| FeatureManagement.FeatureManagementComponent | Features modal | ```bash
| Identity.UsersComponent | Users page | yarn ng generate component shared/my-application-layout --export --entryComponent
| Identity.RolesComponent | Roles page |
| PermissionManagement.PermissionManagementComponent | Permissions modal | # You don't need the --entryComponent option in Angular 9
| SettingManagement.SettingManagementComponent | Setting Management page | ```
| TenantManagement.TenantsComponent | Tenants page |
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 { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents enum for component keys
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: eThemeBasicComponents.ApplicationLayout,
}),
);
//...
}
}
```
## What's Next? ## What's Next?

101
docs/en/UI/Angular/Container-Strategy.md

@ -0,0 +1,101 @@
# ContainerStrategy
`ContainerStrategy` is an abstract class exposed by @abp/ng.core package. There are two container strategies extending it: `ClearContainerStrategy` and `InsertIntoContainerStrategy`. Implementing the same methods and properties, both of these strategies help you define how your containers will be prepared and where your content will be projected.
## API
`ClearContainerStrategy` is a class that extends `ContainerStrategy`. It lets you **clear a container before projecting content in it**.
### constructor
```js
constructor(
public containerRef: ViewContainerRef,
private index?: number, // works only in InsertIntoContainerStrategy
)
```
- `containerRef` is the `ViewContainerRef` that will be used when projecting the content.
### getIndex
```js
getIndex(): number
```
This method return the given index clamped by `0` and `length` of the `containerRef`. For strategies without an index, it returns `0`.
### prepare
```js
prepare(): void
```
This method is called before content projection. Based on used container strategy, it either clears the container or does nothing (noop).
## ClearContainerStrategy
`ClearContainerStrategy` is a class that extends `ContainerStrategy`. It lets you **clear a container before projecting content in it**.
## InsertIntoContainerStrategy
`InsertIntoContainerStrategy` is a class that extends `ContainerStrategy`. It lets you **project your content at a specific node index in the container**.
## Predefined Container Strategies
Predefined container strategies are accessible via `CONTAINER_STRATEGY` constant.
### Clear
```js
CONTAINER_STRATEGY.Clear(containerRef: ViewContainerRef)
```
Clears given container before content projection.
### Append
```js
CONTAINER_STRATEGY.Append(containerRef: ViewContainerRef)
```
Projected content will be appended to the container.
### Prepend
```js
CONTAINER_STRATEGY.Prepend(containerRef: ViewContainerRef)
```
Projected content will be prepended to the container.
### Insert
```js
CONTAINER_STRATEGY.Insert(
containerRef: ViewContainerRef,
index: number,
)
```
Projected content will be inserted into to the container at given index (clamped by `0` and `length` of the `containerRef`).
## See Also
- [ProjectionStrategy](./Projection-Strategy.md)

78
docs/en/UI/Angular/Content-Projection-Service.md

@ -0,0 +1,78 @@
# Content Projection
You can use the `ContentProjectionService` in @abp/ng.core package in order to project content in an easy and explicit way.
## Getting Started
You do not have to provide the `ContentProjectionService` at module or component level, because it is already **provided in root**. You can inject and start using it immediately in your components, directives, or services.
```js
import { ContentProjectionService } from '@abp/ng.core';
@Component({
/* class metadata here */
})
class DemoComponent {
constructor(private contentProjectionService: ContentProjectionService) {}
}
```
## Usage
You can use the `projectContent` method of `ContentProjectionService` to render components and templates dynamically in your project.
### How to Project Components to Root Level
If you pass a `RootComponentProjectionStrategy` as the first parameter of `projectContent` method, the `ContentProjectionService` will resolve the projected component and place it at the root level. If provided, it will also pass the component a context.
```js
const strategy = PROJECTION_STRATEGY.AppendComponentToBody(
SomeOverlayComponent,
{ someOverlayProp: "SOME_VALUE" }
);
const componentRef = this.contentProjectionService.projectContent(strategy);
```
In the example above, `SomeOverlayComponent` component will placed at the **end** of `<body>` and a `ComponentRef` will be returned. Additionally, the given context will be applied, so `someOverlayProp` of the component will be set to `SOME_VALUE`.
> You should keep the returned `ComponentRef` instance, as it is a reference to the projected component and you will need that reference to destroy the projected view and the component instance.
### How to Project Components and Templates into a Container
If you pass a `ComponentProjectionStrategy` or `TemplateProjectionStrategy` as the first parameter of `projectContent` method, and a `ViewContainerRef` as the second parameter of that strategy, the `ContentProjectionService` will project the component or template to the given container. If provided, it will also pass the component or the template a context.
```js
const strategy = PROJECTION_STRATEGY.ProjectComponentToContainer(
SomeComponent,
viewContainerRefOfTarget,
{ someProp: "SOME_VALUE" }
);
const componentRef = this.contentProjectionService.projectContent(strategy);
```
In this example, the `viewContainerRefOfTarget`, which is a `ViewContainerRef` instance, will be cleared and `SomeComponent` component will be placed inside it. In addition, the given context will be applied and `someProp` of the component will be set to `SOME_VALUE`.
> You should keep the returned `ComponentRef` or `EmbeddedViewRef`, as they are a reference to the projected content and you will need them to destroy it when necessary.
Please refer to [ProjectionStrategy](./Projection-Strategy.md) to see all available projection strategies and how you can build your own projection strategy.
## API
### projectContent
```js
projectContent<T extends Type<any> | TemplateRef<any>>(
projectionStrategy: ProjectionStrategy<T>,
injector = this.injector,
): ComponentRef<C> | EmbeddedViewRef<C>
```
- `projectionStrategy` parameter is the primary focus here and is explained above.
- `injector` parameter is the `Injector` instance you can pass to the projected content. It is not used in `TemplateProjectionStrategy`.
## What's Next?
- [TrackByService](./Track-By-Service.md)

74
docs/en/UI/Angular/Content-Security-Strategy.md

@ -0,0 +1,74 @@
# ContentSecurityStrategy
`ContentSecurityStrategy` is an abstract class exposed by @abp/ng.core package. It helps you mark inline scripts or styles as safe in terms of [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy).
## API
### constructor
```js
constructor(public nonce?: string)
```
- `nonce` enables whitelisting inline script or styles in order to avoid using `unsafe-inline` in [script-src](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#Unsafe_inline_script) and [style-src](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src#Unsafe_inline_styles) directives.
### applyCSP
```js
applyCSP(element: HTMLScriptElement | HTMLStyleElement): void
```
This method maps the aforementioned properties to the given `element`.
## LooseContentSecurityPolicy
`LooseContentSecurityPolicy` is a class that extends `ContentSecurityStrategy`. It requires `nonce` and marks given `<script>` or `<style>` tag with it.
## NoContentSecurityPolicy
`NoContentSecurityPolicy` is a class that extends `ContentSecurityStrategy`. It does not mark inline scripts and styles as safe. You can consider it as a noop alternative.
## Predefined Content Security Strategies
Predefined content security strategies are accessible via `CONTENT_SECURITY_STRATEGY` constant.
### Loose
```js
CONTENT_SECURITY_STRATEGY.Loose(nonce: string)
```
`nonce` will be set.
### None
```js
CONTENT_SECURITY_STRATEGY.None()
```
Nothing will be done.
## See Also
- [DomInsertionService](./Dom-Insertion-Service.md)
- [ContentStrategy](./Content-Strategy.md)

95
docs/en/UI/Angular/Content-Strategy.md

@ -0,0 +1,95 @@
# ContentStrategy
`ContentStrategy` is an abstract class exposed by @abp/ng.core package. It helps you create inline scripts or styles.
## API
### constructor
```js
constructor(
public content: string,
protected domStrategy?: DomStrategy,
protected contentSecurityStrategy?: ContentSecurityStrategy
)
```
- `content` is set to `<script>` and `<style>` elements as `textContent` property.
- `domStrategy` is the `DomStrategy` that will be used when inserting the created element. (_default: AppendToHead_)
- `contentSecurityStrategy` is the `ContentSecurityStrategy` that will be used on the created element before inserting it. (_default: None_)
Please refer to [DomStrategy](./Dom-Strategy.md) and [ContentSecurityStrategy](./Content-Security-Strategy.md) documentation for their usage.
### createElement
```js
createElement(): HTMLScriptElement | HTMLStyleElement
```
This method creates and returns a `<script>` or `<style>` element with `content` set as `textContent`.
### insertElement
```js
insertElement(): void
```
This method creates and inserts a `<script>` or `<style>` element.
## ScriptContentStrategy
`ScriptContentStrategy` is a class that extends `ContentStrategy`. It lets you **insert a `<script>` element to the DOM**.
## StyleContentStrategy
`StyleContentStrategy` is a class that extends `ContentStrategy`. It lets you **insert a `<style>` element to the DOM**.
## Predefined Content Strategies
Predefined content strategies are accessible via `CONTENT_STRATEGY` constant.
### AppendScriptToBody
```js
CONTENT_STRATEGY.AppendScriptToBody(content: string)
```
Creates a `<script>` element with the given content and places it at the **end** of `<body>` tag in the document.
### AppendScriptToHead
```js
CONTENT_STRATEGY.AppendScriptToHead(content: string)
```
Creates a `<script>` element with the given content and places it at the **end** of `<head>` tag in the document.
### AppendStyleToHead
```js
CONTENT_STRATEGY.AppendStyleToHead(content: string)
```
Creates a `<style>` element with the given content and places it at the **end** of `<head>` tag in the document.
### PrependStyleToHead
```js
CONTENT_STRATEGY.PrependStyleToHead(content: string)
```
Creates a `<style>` element with the given content and places it at the **beginning** of `<head>` tag in the document.
## See Also
- [DomInsertionService](./Dom-Insertion-Service.md)

117
docs/en/UI/Angular/Context-Strategy.md

@ -0,0 +1,117 @@
# ContextStrategy
`ContextStrategy` is an abstract class exposed by @abp/ng.core package. There are three context strategies extending it: `ComponentContextStrategy`, `TemplateContextStrategy`, and `NoContextStrategy`. Implementing the same methods and properties, all of these strategies help you define how projected content will get their context.
## ComponentContextStrategy
`ComponentContextStrategy` is a class that extends `ContextStrategy`. It lets you **pass context to a projected component**.
### constructor
```js
constructor(public context: Partial<InferredInstanceOf<T>>) {}
```
- `T` refers to component type here, i.e. `Type<C>`.
- `InferredInstanceOf` is a utility type exposed by @abp/ng.core package. It infers component shape.
- `context` will be mapped to properties of the projected component.
### setContext
```js
setContext(componentRef: ComponentRef<InferredInstanceOf<T>>): Partial<InferredInstanceOf<T>>
```
This method maps each prop of the context to the component property with the same name and calls change detection. It returns the context after mapping.
## TemplateContextStrategy
`TemplateContextStrategy` is a class that extends `ContextStrategy`. It lets you **pass context to a projected template**.
### constructor
```js
constructor(public context: Partial<InferredContextOf<T>>) {}
```
- `T` refers to template context type here, i.e. `TemplateRef<C>`.
- `InferredContextOf` is a utility type exposed by @abp/ng.core package. It infers context shape.
- `context` will be mapped to properties of the projected template.
### setContext
```js
setContext(): Partial<InferredContextOf<T>>
```
This method does nothing and only returns the context, because template context is not mapped but passed in as parameter to `createEmbeddedView` method.
## NoContextStrategy
`NoContextStrategy` is a class that extends `ContextStrategy`. It lets you **skip passing any context to projected content**.
### constructor
```js
constructor()
```
Unlike other context strategies, `NoContextStrategy` contructor takes no parameters.
### setContext
```js
setContext(): undefined
```
Since there is no context, this method gets no parameters and will return `undefined`.
## Predefined Context Strategies
Predefined context strategies are accessible via `CONTEXT_STRATEGY` constant.
### None
```js
CONTEXT_STRATEGY.None()
```
This strategy will not pass any context to the projected content.
### Component
```js
CONTEXT_STRATEGY.Component(context: Partial<InferredContextOf<T>>)
```
This strategy will help you pass the given context to the projected component.
### Template
```js
CONTEXT_STRATEGY.Template(context: Partial<InferredContextOf<T>>)
```
This strategy will help you pass the given context to the projected template.
## See Also
- [ProjectionStrategy](./Projection-Strategy.md)

60
docs/en/UI/Angular/Cross-Origin-Strategy.md

@ -0,0 +1,60 @@
# CrossOriginStrategy
`CrossOriginStrategy` is a class exposed by @abp/ng.core package. Its instances define how a source referenced by an element will be retrieved by the browser and are consumed by other classes such as `LoadingStrategy`.
## API
### constructor
```js
constructor(
public crossorigin: 'anonymous' | 'use-credentials',
public integrity?: string
)
```
- `crossorigin` is mapped to [the HTML attribute with the same name](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/crossorigin).
- `integrity` is a hash for validating a remote resource. Its use is explained [here](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity).
### setCrossOrigin
```js
setCrossOrigin(element: HTMLElement): void
```
This method maps the aforementioned properties to the given `element`.
## Predefined Cross-Origin Strategies
Predefined cross-origin strategies are accessible via `CROSS_ORIGIN_STRATEGY` constant.
### Anonymous
```js
CROSS_ORIGIN_STRATEGY.Anonymous(integrity?: string)
```
`crossorigin` will be set as `"anonymous"` and `integrity` is optional.
### UseCredentials
```js
CROSS_ORIGIN_STRATEGY.UseCredentials(integrity?: string)
```
`crossorigin` will be set as `"use-credentials"` and `integrity` is optional.
## What's Next?
- [LoadingStrategy](./Loading-Strategy.md)

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

@ -43,4 +43,4 @@ Navigate to `/setting-management` route to see the changes:
## What's Next? ## What's Next?
- [TrackByService](./Track-By-Service.md) - [Lazy Loading Scripts & Styles](./Lazy-Load-Service.md)

87
docs/en/UI/Angular/Dom-Insertion-Service.md

@ -0,0 +1,87 @@
# Dom Insertion (of Scripts and Styles)
You can use the `DomInsertionService` in @abp/ng.core package in order to insert scripts and styles in an easy and explicit way.
## Getting Started
You do not have to provide the `DomInsertionService` at module or component level, because it is already **provided in root**. You can inject and start using it immediately in your components, directives, or services.
```js
import { DomInsertionService } from '@abp/ng.core';
@Component({
/* class metadata here */
})
class DemoComponent {
constructor(private domInsertionService: DomInsertionService) {}
}
```
## Usage
You can use the `insertContent` method of `DomInsertionService` to create a `<script>` or `<style>` element with given content in the DOM at the desired position. There is also the `projectContent` method for dynamically rendering components and templates.
### How to Insert Scripts
The first parameter of `insertContent` method expects a `ContentStrategy`. If you pass a `ScriptContentStrategy` instance, the `DomInsertionService` will create a `<script>` element with given `content` and place it in the designated DOM position.
```js
import { DomInsertionService, CONTENT_STRATEGY } from '@abp/ng.core';
@Component({
/* class metadata here */
})
class DemoComponent {
constructor(private domInsertionService: DomInsertionService) {}
ngOnInit() {
this.domInsertionService.insertContent(
CONTENT_STRATEGY.AppendScriptToBody('alert()')
);
}
}
```
In the example above, `<script>alert()</script>` element will place at the **end** of `<body>`.
Please refer to [ContentStrategy](./Content-Strategy.md) to see all available content strategies and how you can build your own content strategy.
### How to Insert Styles
If you pass a `StyleContentStrategy` instance as the first parameter of `insertContent` method, the `DomInsertionService` will create a `<style>` element with given `content` and place it in the designated DOM position.
```js
import { DomInsertionService, CONTENT_STRATEGY } from '@abp/ng.core';
@Component({
/* class metadata here */
})
class DemoComponent {
constructor(private domInsertionService: DomInsertionService) {}
ngOnInit() {
this.domInsertionService.insertContent(
CONTENT_STRATEGY.AppendStyleToHead('body {margin: 0;}')
);
}
}
```
In the example above, `<style>body {margin: 0;}</style>` element will place at the **end** of `<head>`.
Please refer to [ContentStrategy](./Content-Strategy.md) to see all available content strategies and how you can build your own content strategy.
## API
### insertContent
```js
insertContent(contentStrategy: ContentStrategy): void
```
- `contentStrategy` parameter is the primary focus here and is explained above.
## What's Next?
- [ContentProjectionService](./Content-Projection-Service.md)

90
docs/en/UI/Angular/Dom-Strategy.md

@ -0,0 +1,90 @@
# DomStrategy
`DomStrategy` is a class exposed by @abp/ng.core package. Its instances define how an element will be attached to the DOM and are consumed by other classes such as `LoadingStrategy`.
## API
### constructor
```js
constructor(
public target?: HTMLElement,
public position?: InsertPosition
)
```
- `target` is an HTMLElement (_default: document.head_).
- `position` defines where the created element will be placed. All possible values of `position` can be found [here](https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentElement) (_default: 'beforeend'_).
### insertElement
```js
insertElement(element: HTMLElement): void
```
This method inserts given `element` to `target` based on the `position`.
## Predefined Dom Strategies
Predefined dom strategies are accessible via `DOM_STRATEGY` constant.
### AppendToBody
```js
DOM_STRATEGY.AppendToBody()
```
`insertElement` will place the given `element` at the end of `<body>`.
### AppendToHead
```js
DOM_STRATEGY.AppendToHead()
```
`insertElement` will place the given `element` at the end of `<head>`.
### PrependToHead
```js
DOM_STRATEGY.PrependToHead()
```
`insertElement` will place the given `element` at the beginning of `<head>`.
### AfterElement
```js
DOM_STRATEGY.AfterElement(target: HTMLElement)
```
`insertElement` will place the given `element` after (as a sibling to) the `target`.
### BeforeElement
```js
DOM_STRATEGY.BeforeElement(target: HTMLElement)
```
`insertElement` will place the given `element` before (as a sibling to) the `target`.
## See Also
- [DomInsertionService](./Dom-Insertion-Service.md)
- [LazyLoadService](./Lazy-Load-Service.md)
- [LoadingStrategy](./Loading-Strategy.md)
- [ContentStrategy](./Content-Strategy.md)
- [ProjectionStrategy](./Projection-Strategy.md)

213
docs/en/UI/Angular/Lazy-Load-Service.md

@ -0,0 +1,213 @@
# How to Lazy Load Scripts and Styles
You can use the `LazyLoadService` in @abp/ng.core package in order to lazy load scripts and styles in an easy and explicit way.
## Getting Started
You do not have to provide the `LazyLoadService` at module or component level, because it is already **provided in root**. You can inject and start using it immediately in your components, directives, or services.
```js
import { LazyLoadService } from '@abp/ng.core';
@Component({
/* class metadata here */
})
class DemoComponent {
constructor(private lazyLoadService: LazyLoadService) {}
}
```
## Usage
You can use the `load` method of `LazyLoadService` to create a `<script>` or `<link>` element in the DOM at the desired position and force the browser to download the target resource.
### How to Load Scripts
The first parameter of `load` method expects a `LoadingStrategy`. If you pass a `ScriptLoadingStrategy` instance, the `LazyLoadService` will create a `<script>` element with given `src` and place it in the designated DOM position.
```js
import { LazyLoadService, LOADING_STRATEGY } from '@abp/ng.core';
@Component({
template: `
<some-component *ngIf="libraryLoaded$ | async"></some-component>
`
})
class DemoComponent {
libraryLoaded$ = this.lazyLoad.load(
LOADING_STRATEGY.AppendAnonymousScriptToHead('/assets/some-library.js'),
);
constructor(private lazyLoadService: LazyLoadService) {}
}
```
The `load` method returns an observable to which you can subscibe in your component or with an `async` pipe. In the example above, the `NgIf` directive will render `<some-component>` only **if the script gets successfully loaded or is already loaded before**.
> You can subscribe multiple times in your template with `async` pipe. The styles will only be loaded once.
Please refer to [LoadingStrategy](./Loading-Strategy.md) to see all available loading strategies and how you can build your own loading strategy.
### How to Load Styles
If you pass a `StyleLoadingStrategy` instance as the first parameter of `load` method, the `LazyLoadService` will create a `<link>` element with given `href` and place it in the designated DOM position.
```js
import { LazyLoadService, LOADING_STRATEGY } from '@abp/ng.core';
@Component({
template: `
<some-component *ngIf="stylesLoaded$ | async"></some-component>
`
})
class DemoComponent {
stylesLoaded$ = this.lazyLoad.load(
LOADING_STRATEGY.AppendAnonymousStyleToHead('/assets/some-styles.css'),
);
constructor(private lazyLoadService: LazyLoadService) {}
}
```
The `load` method returns an observable to which you can subscibe in your component or with an `AsyncPipe`. In the example above, the `NgIf` directive will render `<some-component>` only **if the style gets successfully loaded or is already loaded before**.
> You can subscribe multiple times in your template with `async` pipe. The styles will only be loaded once.
Please refer to [LoadingStrategy](./Loading-Strategy.md) to see all available loading strategies and how you can build your own loading strategy.
### Advanced Usage
You have quite a bit of **freedom to define how your lazy load will work**. Here is an example:
```js
const domStrategy = DOM_STRATEGY.PrependToHead();
const crossOriginStrategy = CROSS_ORIGIN_STRATEGY.Anonymous(
'sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh',
);
const loadingStrategy = new StyleLoadingStrategy(
'https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css',
domStrategy,
crossOriginStrategy,
);
this.lazyLoad.load(loadingStrategy, 1, 2000);
```
This code will create a `<link>` element with given url and integrity hash, insert it to to top of the `<head>` element, and retry once after 2 seconds if first try fails.
A common usecase is **loading multiple scripts and/or styles before using a feature**:
```js
import { LazyLoadService, LOADING_STRATEGY } from '@abp/ng.core';
import { frokJoin } from 'rxjs';
@Component({
template: `
<some-component *ngIf="scriptsAndStylesLoaded$ | async"></some-component>
`
})
class DemoComponent {
private stylesLoaded$ = forkJoin(
this.lazyLoad.load(
LOADING_STRATEGY.PrependAnonymousStyleToHead('/assets/library-dark-theme.css'),
),
this.lazyLoad.load(
LOADING_STRATEGY.PrependAnonymousStyleToHead('/assets/library.css'),
),
);
private scriptsLoaded$ = forkJoin(
this.lazyLoad.load(
LOADING_STRATEGY.AppendAnonymousScriptToHead('/assets/library.js'),
),
this.lazyLoad.load(
LOADING_STRATEGY.AppendAnonymousScriptToHead('/assets/other-library.css'),
),
);
scriptsAndStylesLoaded$ = forkJoin(this.scriptsLoaded$, this.stylesLoaded$);
constructor(private lazyLoadService: LazyLoadService) {}
}
```
RxJS `forkJoin` will load all scripts and styles in parallel and emit only when all of them are loaded. So, when `<some-component>` is placed, all required dependencies will be available.
> Noticed we have prepended styles to the document head? This is sometimes necessary, because your application styles may be overriding some of the library styles. In such a case, you must be careful about the order of prepended styles. They will be placed one-by-one and, **when prepending, the last one placed will be on top**.
Another frequent usecase is **loading dependent scripts in order**:
```js
import { LazyLoadService, LOADING_STRATEGY } from '@abp/ng.core';
import { concat } from 'rxjs';
@Component({
template: `
<some-component *ngIf="scriptsLoaded$ | async"></some-component>
`
})
class DemoComponent {
scriptsLoaded$ = concat(
this.lazyLoad.load(
LOADING_STRATEGY.PrependAnonymousScriptToHead('/assets/library.js'),
),
this.lazyLoad.load(
LOADING_STRATEGY.AppendAnonymousScriptToHead('/assets/script-that-requires-library.js'),
),
);
constructor(private lazyLoadService: LazyLoadService) {}
}
```
In this example, the second file needs the first one to be loaded beforehand. RxJS `concat` function will let you load all scripts one-by-one in the given order and emit only when all of them are loaded.
## API
### loaded
```js
loaded: Set<string>
```
All previously loaded paths are available via this property. It is a simple [JavaScript Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set).
### load
```js
load(strategy: LoadingStrategy, retryTimes?: number, retryDelay?: number): Observable<Event>
```
- `strategy` parameter is the primary focus here and is explained above.
- `retryTimes` defines how many times the loading will be tried again before fail (_default: 2_).
- `retryDelay` defines how much delay there will be between retries (_default: 1000_).
## What's Next?
- [DomInsertionService](./Dom-Insertion-Service.md)

1284
docs/en/UI/Angular/Linked-List.md

File diff suppressed because it is too large

110
docs/en/UI/Angular/Loading-Strategy.md

@ -0,0 +1,110 @@
# LoadingStrategy
`LoadingStrategy` is an abstract class exposed by @abp/ng.core package. There are two loading strategies extending it: `ScriptLoadingStrategy` and `StyleLoadingStrategy`. Implementing the same methods and properties, both of these strategies help you define how your lazy loading will work.
## API
### constructor
```js
constructor(
public path: string,
protected domStrategy?: DomStrategy,
protected crossOriginStrategy?: CrossOriginStrategy
)
```
- `path` is set to `<script>` elements as `src` and `<link>` elements as `href` attribute.
- `domStrategy` is the `DomStrategy` that will be used when inserting the created element. (_default: AppendToHead_)
- `crossOriginStrategy` is the `CrossOriginStrategy` that will be used on the created element before inserting it. (_default: Anonymous_)
Please refer to [DomStrategy](./Dom-Strategy.md) and [CrossOriginStrategy](./Cross-Origin-Strategy.md) documentation for their usage.
### createElement
```js
createElement(): HTMLScriptElement | HTMLLinkElement
```
This method creates and returns a `<script>` or `<link>` element with `path` set as `src` or `href`.
### createStream
```js
createStream(): Observable<Event>
```
This method creates and returns an observable stream that emits on success and throws on error.
## ScriptLoadingStrategy
`ScriptLoadingStrategy` is a class that extends `LoadingStrategy`. It lets you **lazy load a script**.
## StyleLoadingStrategy
`StyleLoadingStrategy` is a class that extends `LoadingStrategy`. It lets you **lazy load a style**.
## Predefined Loading Strategies
Predefined loading strategies are accessible via `LOADING_STRATEGY` constant.
### AppendAnonymousScriptToHead
```js
LOADING_STRATEGY.AppendAnonymousScriptToHead(src: string, integrity?: string)
```
Sets given paremeters and `crossorigin="anonymous"` as attributes of created `<script>` element and places it at the **end** of `<head>` tag in the document.
### PrependAnonymousScriptToHead
```js
LOADING_STRATEGY.PrependAnonymousScriptToHead(src: string, integrity?: string)
```
Sets given paremeters and `crossorigin="anonymous"` as attributes of created `<script>` element and places it at the **beginning** of `<head>` tag in the document.
### AppendAnonymousScriptToBody
```js
LOADING_STRATEGY.AppendAnonymousScriptToBody(src: string, integrity?: string)
```
Sets given paremeters and `crossorigin="anonymous"` as attributes of created `<script>` element and places it at the **end** of `<body>` tag in the document.
### AppendAnonymousStyleToHead
```js
LOADING_STRATEGY.AppendAnonymousStyleToHead(href: string, integrity?: string)
```
Sets given paremeters and `crossorigin="anonymous"` as attributes of created `<style>` element and places it at the **end** of `<head>` tag in the document.
### PrependAnonymousStyleToHead
```js
LOADING_STRATEGY.PrependAnonymousStyleToHead(href: string, integrity?: string)
```
Sets given paremeters and `crossorigin="anonymous"` as attributes of created `<style>` element and places it at the **beginning** of `<head>` tag in the document.
## See Also
- [LazyLoadService](./Lazy-Load-Service.md)

200
docs/en/UI/Angular/Projection-Strategy.md

@ -0,0 +1,200 @@
# ProjectionStrategy
`ProjectionStrategy` is an abstract class exposed by @abp/ng.core package. There are three projection strategies extending it: `ComponentProjectionStrategy`, `RootComponentProjectionStrategy`, and `TemplateProjectionStrategy`. Implementing the same methods and properties, all of these strategies help you define how your content projection will work.
## ComponentProjectionStrategy
`ComponentProjectionStrategy` is a class that extends `ProjectionStrategy`. It lets you **project a component into a container**.
### constructor
```js
constructor(
component: T,
private containerStrategy: ContainerStrategy,
private contextStrategy?: ContextStrategy,
)
```
- `component` is class of the component you would like to project.
- `containerStrategy` is the `ContainerStrategy` that will be used when projecting the component.
- `contextStrategy` is the `ContextStrategy` that will be used on the projected component. (_default: None_)
Please refer to [ContainerStrategy](./Container-Strategy.md) and [ContextStrategy](./Context-Strategy.md) documentation for their usage.
### injectContent
```js
injectContent(injector: Injector): ComponentRef<T>
```
This method prepares the container, resolves the component, sets its context, and projects it to the container. It returns a `ComponentRef` instance, which you should keep in order to clear projected components later on.
## RootComponentProjectionStrategy
`RootComponentProjectionStrategy` is a class that extends `ProjectionStrategy`. It lets you **project a component into the document**, such as appending it to `<body>`.
### constructor
```js
constructor(
component: T,
private contextStrategy?: ContextStrategy,
private domStrategy?: DomStrategy,
)
```
- `component` is class of the component you would like to project.
- `contextStrategy` is the `ContextStrategy` that will be used on the projected component. (_default: None_)
- `domStrategy` is the `DomStrategy` that will be used when inserting component. (_default: AppendToBody_)
Please refer to [ContextStrategy](./Context-Strategy.md) and [DomStrategy](./Dom-Strategy.md) documentation for their usage.
### injectContent
```js
injectContent(injector: Injector): ComponentRef<T>
```
This method resolves the component, sets its context, and projects it to the document. It returns a `ComponentRef` instance, which you should keep in order to clear projected components later on.
## TemplateProjectionStrategy
`TemplateProjectionStrategy` is a class that extends `ProjectionStrategy`. It lets you **project a template into a container**.
### constructor
```js
constructor(
template: T,
private containerStrategy: ContainerStrategy,
private contextStrategy?: ContextStrategy,
)
```
- `template` is `TemplateRef` you would like to project.
- `containerStrategy` is the `ContainerStrategy` that will be used when projecting the component.
- `contextStrategy` is the `ContextStrategy` that will be used on the projected component. (_default: None_)
Please refer to [ContainerStrategy](./Container-Strategy.md) and [ContextStrategy](./Context-Strategy.md) documentation for their usage.
### injectContent
```js
injectContent(): EmbeddedViewRef<T>
```
This method prepares the container, and projects the template together with the defined context to it. It returns an `EmbeddedViewRef`, which you should keep in order to clear projected templates later on.
## Predefined Projection Strategies
Predefined projection strategies are accessible via `PROJECTION_STRATEGY` constant.
### AppendComponentToBody
```js
PROJECTION_STRATEGY.AppendComponentToBody(
component: T,
contextStrategy?: ComponentContextStrategy<T>,
)
```
Sets given context to the component and places it at the **end** of `<body>` tag in the document.
### AppendComponentToContainer
```js
PROJECTION_STRATEGY.AppendComponentToContainer(
component: T,
containerRef: ViewContainerRef,
contextStrategy?: ComponentContextStrategy<T>,
)
```
Sets given context to the component and places it at the **end** of the container.
### AppendTemplateToContainer
```js
PROJECTION_STRATEGY.AppendTemplateToContainer(
templateRef: T,
containerRef: ViewContainerRef,
contextStrategy?: ComponentContextStrategy<T>,
)
```
Sets given context to the template and places it at the **end** of the container.
### PrependComponentToContainer
```js
PROJECTION_STRATEGY.PrependComponentToContainer(
component: T,
containerRef: ViewContainerRef,
contextStrategy?: ComponentContextStrategy<T>,
)
```
Sets given context to the component and places it at the **beginning** of the container.
### PrependTemplateToContainer
```js
PROJECTION_STRATEGY.PrependTemplateToContainer(
templateRef: T,
containerRef: ViewContainerRef,
contextStrategy?: ComponentContextStrategy<T>,
)
```
Sets given context to the template and places it at the **beginning** of the container.
### ProjectComponentToContainer
```js
PROJECTION_STRATEGY.ProjectComponentToContainer(
component: T,
containerRef: ViewContainerRef,
contextStrategy?: ComponentContextStrategy<T>,
)
```
Clears the container, sets given context to the component, and places it **in the cleared** the container.
### ProjectTemplateToContainer
```js
PROJECTION_STRATEGY.ProjectTemplateToContainer(
templateRef: T,
containerRef: ViewContainerRef,
contextStrategy?: ComponentContextStrategy<T>,
)
```
Clears the container, sets given context to the template, and places it **in the cleared** the container.
## See Also
- [DomInsertionService](./Dom-Insertion-Service.md)

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

@ -25,9 +25,9 @@ The files generated with the `--module all` option like below:
### Services ### Services
Each generated service matches a back-end controller. The services methods call back-end APIs via [RestService](./Http-Requests.md#restservice). 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.md#how-to-get-a-specific-api-endpoint-from-application-config) 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: 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:
@ -64,4 +64,4 @@ Initial values ​​can optionally be passed to each class constructor.
## What's Next? ## What's Next?
* [HTTP Requests](./Http-Requests.md) * [HTTP Requests](./Http-Requests)

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

@ -111,7 +111,3 @@ class DemoComponent {
trackByTenantAccountId = trackByDeep<Item>('tenant', 'account', 'id'); trackByTenantAccountId = trackByDeep<Item>('tenant', 'account', 'id');
} }
``` ```
## What's Next?
* [Linked List (Doubly)](./Linked-List.md)

4
docs/en/UI/AspNetCore/Bundling-Minification.md

@ -246,10 +246,10 @@ public class MyPrismjsStyleExtension : BundleContributor
} }
```` ````
Then you can configure `BundleContributorOptions` to extend existing `PrismjsStyleBundleContributor`. Then you can configure `AbpBundleContributorOptions` to extend existing `PrismjsStyleBundleContributor`.
````csharp ````csharp
Configure<BundleContributorOptions>(options => Configure<AbpBundleContributorOptions>(options =>
{ {
options options
.Extensions<PrismjsStyleBundleContributor>() .Extensions<PrismjsStyleBundleContributor>()

42
docs/en/UI/AspNetCore/Tag-Helpers/Popovers.md

@ -0,0 +1,42 @@
# Popovers
## Introduction
`abp-popover` is the abp tag for popover messages.
Basic usage:
````xml
<abp-button abp-popover="Hi, i'm popover content!">
Popover Default
</abp-button>
````
## Demo
See the [popovers demo page](https://bootstrap-taghelpers.abp.io/Components/Popovers) to see it in action.
## Attributes
### disabled
A value indicates if the element should be disabled for interaction. If this value is set to `true`, `dismissable` attribute will be ignored. Should be one of the following values:
* `false` (default value)
* `true`
### dismissable
A value indicates to dismiss the popovers on the user's next click of a different element than the toggle element. Should be one of the following values:
* `false` (default value)
* `true`
### hoverable
A value indicates if the popover content will be displayed on mouse hover. Should be one of the following values:
* `false` (default value)
* `true`

2
docs/en/UI/AspNetCore/Widgets.md

@ -502,4 +502,4 @@ Configure<AbpWidgetOptions>(options =>
## See Also ## See Also
* [Example project (source code)](https://github.com/abpframework/abp/tree/dev/samples/DashboardDemo). * [Example project (source code)](https://github.com/abpframework/abp-samples/tree/master/DashboardDemo).

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

File diff suppressed because it is too large

29
docs/en/docs-nav.json

@ -75,6 +75,10 @@
} }
] ]
}, },
{
"text": "\"How to\" Guides",
"path": "How-To/Index.md"
},
{ {
"text": "Migrating from the ASP.NET Boilerplate", "text": "Migrating from the ASP.NET Boilerplate",
"path": "AspNet-Boilerplate-Migration-Guide.md" "path": "AspNet-Boilerplate-Migration-Guide.md"
@ -151,6 +155,10 @@
{ {
"text": "Data Filtering", "text": "Data Filtering",
"path": "Data-Filtering.md" "path": "Data-Filtering.md"
},
{
"text": "Object Extensions",
"path": "Object-Extensions.md"
} }
] ]
}, },
@ -341,13 +349,30 @@
"text": "Custom Setting Page", "text": "Custom Setting Page",
"path": "UI/Angular/Custom-Setting-Page.md" "path": "UI/Angular/Custom-Setting-Page.md"
}, },
{
"text": "Lazy Loading Scripts & Styles",
"path": "UI/Angular/Lazy-Load-Service.md"
},
{
"text": "DomInsertionService",
"path": "UI/Angular/Dom-Insertion-Service.md"
},
{
"text": "ContentProjectionService",
"path": "UI/Angular/Content-Projection-Service.md"
},
{ {
"text": "TrackByService", "text": "TrackByService",
"path": "UI/Angular/Track-By-Service.md" "path": "UI/Angular/Track-By-Service.md"
}, }
]
},
{
"text": "Common",
"items": [
{ {
"text": "Linked List (Doubly)", "text": "Linked List (Doubly)",
"path": "UI/Angular/Linked-List.md" "path": "UI/Common/Utils/Linked-List.md"
} }
] ]
} }

BIN
docs/en/images/angular-folder-structure.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

BIN
docs/en/images/angular-template-structure-diagram.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

BIN
docs/en/images/react-native-folder-structure.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
docs/en/images/react-native-navigation-structure.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
docs/en/images/react-native-store-folder.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

2
docs/pt-BR/Tutorials/Angular/Part-I.md

@ -10,7 +10,7 @@ Esta é a primeira parte da série de tutoriais angulares. Veja todas as peças:
- [Parte II: Criar, atualizar e excluir livros](Part-II) - [Parte II: Criar, atualizar e excluir livros](Part-II)
- [Parte III: Testes de Integração](Part-III) - [Parte III: Testes de Integração](Part-III)
Você pode acessar o **código fonte** do aplicativo no [repositório GitHub](https://github.com/abpframework/abp/tree/dev/samples/BookStore-Angular-MongoDb) . Você pode acessar o **código fonte** do aplicativo no [repositório GitHub](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) .
### Criando o projeto ### Criando o projeto

2
docs/pt-BR/Tutorials/Angular/Part-II.md

@ -8,7 +8,7 @@ Esta é a segunda parte da série de tutoriais angulares. Veja todas as peças:
- **Parte II: Criar, atualizar e excluir livros (este tutorial)** - **Parte II: Criar, atualizar e excluir livros (este tutorial)**
- [Parte III: Testes de Integração](Part-III.md) - [Parte III: Testes de Integração](Part-III.md)
Você pode acessar o **código fonte** do aplicativo no [repositório GitHub](https://github.com/abpframework/abp/tree/dev/samples/BookStore-Angular-MongoDb) . Você pode acessar o **código fonte** do aplicativo no [repositório GitHub](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) .
### Criando um novo livro ### Criando um novo livro

2
docs/pt-BR/Tutorials/Angular/Part-III.md

@ -8,7 +8,7 @@ Esta é a terceira parte da série de tutoriais Angular. Veja todas as peças:
- [Parte II: Criar, atualizar e excluir livros](Part-II.md) - [Parte II: Criar, atualizar e excluir livros](Part-II.md)
- **Parte III: Testes de Integração (este tutorial)** - **Parte III: Testes de Integração (este tutorial)**
Esta parte abrange os testes do **lado** do **servidor** . Você pode acessar o **código fonte** do aplicativo no [repositório GitHub](https://github.com/abpframework/abp/tree/dev/samples/BookStore-Angular-MongoDb) . Esta parte abrange os testes do **lado** do **servidor** . Você pode acessar o **código fonte** do aplicativo no [repositório GitHub](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) .
### Testar projetos na solução ### Testar projetos na solução

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

@ -10,7 +10,7 @@ Esta é a primeira parte da série de tutoriais do ASP.NET Core MVC. Veja todas
- [Parte II: Criar, atualizar e excluir livros](Part-II.md) - [Parte II: Criar, atualizar e excluir livros](Part-II.md)
- [Parte III: Testes de Integração](Part-III.md) - [Parte III: Testes de Integração](Part-III.md)
Você pode acessar o **código fonte** do aplicativo [no repositório GitHub](https://github.com/abpframework/abp/tree/master/samples/BookStore) . Você pode acessar o **código fonte** do aplicativo [no repositório GitHub](https://github.com/abpframework/abp-samples/tree/master/BookStore) .
> Você também pode assistir a [este curso em vídeo](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application) preparado por um membro da comunidade ABP, com base neste tutorial. > Você também pode assistir a [este curso em vídeo](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application) preparado por um membro da comunidade ABP, com base neste tutorial.

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

@ -8,7 +8,7 @@ Esta é a segunda parte da série de tutoriais do ASP.NET Core MVC. Veja todas a
- **Parte II: Criar, atualizar e excluir livros (este tutorial)** - **Parte II: Criar, atualizar e excluir livros (este tutorial)**
- [Parte III: Testes de Integração](https://docs.abp.io/en/abp/latest/Tutorials/AspNetCore-Mvc/Part-III) - [Parte III: Testes de Integração](https://docs.abp.io/en/abp/latest/Tutorials/AspNetCore-Mvc/Part-III)
Você pode acessar o **código fonte** do aplicativo [no repositório GitHub](https://github.com/volosoft/abp/tree/master/samples/BookStore) . Você pode acessar o **código fonte** do aplicativo [no repositório GitHub](https://github.com/abpframework/abp-samples/tree/master/BookStore) .
> Você também pode assistir a [este curso em vídeo](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application) preparado por um membro da comunidade ABP, com base neste tutorial. > Você também pode assistir a [este curso em vídeo](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application) preparado por um membro da comunidade ABP, com base neste tutorial.

2
docs/pt-BR/Tutorials/AspNetCore-Mvc/Part-III.md

@ -8,7 +8,7 @@ Esta é a terceira parte da série de tutoriais do ASP.NET Core MVC. Veja todas
- [Parte II: Criar, atualizar e excluir livros](https://docs.abp.io/en/abp/latest/Tutorials/AspNetCore-Mvc/Part-II) - [Parte II: Criar, atualizar e excluir livros](https://docs.abp.io/en/abp/latest/Tutorials/AspNetCore-Mvc/Part-II)
- **Parte III: Testes de Integração (este tutorial)** - **Parte III: Testes de Integração (este tutorial)**
Você pode acessar o **código fonte** do aplicativo [no repositório GitHub](https://github.com/volosoft/abp/tree/master/samples/BookStore) . Você pode acessar o **código fonte** do aplicativo [no repositório GitHub](https://github.com/abpframework/abp-samples/tree/master/BookStore).
> Você também pode assistir a [este curso em vídeo](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application) preparado por um membro da comunidade ABP, com base neste tutorial. > Você também pode assistir a [este curso em vídeo](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application) preparado por um membro da comunidade ABP, com base neste tutorial.

4
docs/zh-Hans/API/Dynamic-CSharp-API-Clients.md

@ -69,7 +69,7 @@ public class MyClientAppModule : AbpModule
} }
```` ````
查看下面的"RemoteServiceOptions"章节获取更多详细配置. 查看下面的"AbpRemoteServiceOptions"章节获取更多详细配置.
## 使用 ## 使用
@ -104,7 +104,7 @@ public class MyService : ITransientDependency
## 配置 ## 配置
### RemoteServiceOptions ### AbpRemoteServiceOptions
默认情况下`AbpRemoteServiceOptions`从`appsettings.json`获取.或者,你可以使用`Configure`方法来设置或重写它.如: 默认情况下`AbpRemoteServiceOptions`从`appsettings.json`获取.或者,你可以使用`Configure`方法来设置或重写它.如:

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

@ -146,7 +146,6 @@ public interface IBookAppService : IApplicationService
`BookDto`是一个简单的[DTO](Data-Transfer-Objects.md)类, 定义如下: `BookDto`是一个简单的[DTO](Data-Transfer-Objects.md)类, 定义如下:
````csharp ````csharp
[AbpAutoMapFrom(typeof(Book))] //Defines the mapping
public class BookDto public class BookDto
{ {
public Guid Id { get; set; } public Guid Id { get; set; }
@ -159,7 +158,36 @@ public class BookDto
} }
```` ````
* `BookDto`定义了`[AbpAutoMapFrom(typeof(Book))]`属性来从创建对象映射Book到BookDto. 我们创建一个Automapper的[Profile](https://docs.automapper.org/en/stable/Configuration.html#profile-instances)类. 例如:
```csharp
public class MyProfile : Profile
{
public MyProfile()
{
CreateMap<Book, BookDto>();
}
}
```
然后使用`AbpAutoMapperOptions`注册配置文件:
````csharp
[DependsOn(typeof(AbpAutoMapperModule))]
public class MyModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpAutoMapperOptions>(options =>
{
//Add all mappings defined in the assembly of the MyModule class
options.AddMaps<MyModule>();
});
}
}
````
`AddMaps` 注册给定类的程序集中所有的配置类,通常使用模块类. 它还会注册 [attribute 映射](https://docs.automapper.org/en/stable/Attribute-mapping.html). 更多信息请参考[对象到对象映射](Object-To-Object-Mapping.md)文档
然后你可以实现`GetAsync`方法. 如下所示: 然后你可以实现`GetAsync`方法. 如下所示:
@ -249,7 +277,6 @@ public interface ICrudAppService<
示例中使用的DTO类是`BookDto`和`CreateUpdateBookDto`: 示例中使用的DTO类是`BookDto`和`CreateUpdateBookDto`:
````csharp ````csharp
[AbpAutoMapFrom(typeof(Book))]
public class BookDto : AuditedEntityDto<Guid> public class BookDto : AuditedEntityDto<Guid>
{ {
public string Name { get; set; } public string Name { get; set; }
@ -259,7 +286,6 @@ public class BookDto : AuditedEntityDto<Guid>
public float Price { get; set; } public float Price { get; set; }
} }
[AbpAutoMapTo(typeof(Book))]
public class CreateUpdateBookDto public class CreateUpdateBookDto
{ {
[Required] [Required]
@ -274,6 +300,19 @@ public class CreateUpdateBookDto
} }
```` ````
DTO类的[Profile](https://docs.automapper.org/en/stable/Configuration.html#profile-instances)类.
```csharp
public class MyProfile : Profile
{
public MyProfile()
{
CreateMap<Book, BookDto>();
CreateMap<CreateUpdateBookDto, Book>();
}
}
```
* `CreateUpdateBookDto`由创建和更新操作共享,但你也可以使用单独的DTO类. * `CreateUpdateBookDto`由创建和更新操作共享,但你也可以使用单独的DTO类.
最后`BookAppService`实现非常简单: 最后`BookAppService`实现非常简单:

30
docs/zh-Hans/Authorization.md

@ -143,6 +143,20 @@ myGroup.AddPermission(
); );
```` ````
#### 启用/禁用权限
权限默认为启用. 它也可以被禁用,禁用权限所有的用户将无法使用它. 你仍然可以检查这个权限,但它总是会返回被禁止.
定义示例:
````csharp
myGroup.AddPermission("Author_Management", isEnabled: false);
````
通常你不需要定义禁用权限(除非您暂时想要禁用应用程序的功能). 无论怎样,你可能想要禁用依赖模块中定义的权限,这样你可以禁用相关的功能. 参阅下面的 "*更改依赖模块的权限定义*" 节,查看示例用法.
> 注意:检查一个未定义的权限会抛出异常,而被禁用的权限的返回禁止(false).
#### 子权限 #### 子权限
权限可以具有子权限,当你想要创建一个层次结构的权限树时它特别有用. 在这个树中一个权限可能含有子权限,并且子权限只有在授权父权限时才可用. 权限可以具有子权限,当你想要创建一个层次结构的权限树时它特别有用. 在这个树中一个权限可能含有子权限,并且子权限只有在授权父权限时才可用.
@ -209,6 +223,18 @@ public class AuthorAppService : ApplicationService, IAuthorAppService
从 `PermissionDefinitionProvider` 派生的类(就像上面的示例一样) 可以获取现有的权限定义(由依赖[模块](Module-Development-Basics.md)定义)并更改其定义. 从 `PermissionDefinitionProvider` 派生的类(就像上面的示例一样) 可以获取现有的权限定义(由依赖[模块](Module-Development-Basics.md)定义)并更改其定义.
示例:
````csharp
context
.GetPermissionOrNull(IdentityPermissions.Roles.Delete)
.IsEnabled = false;
````
当你在权限提供程序编写了这行代码,它会找到[身份模块](Modules/Identity.md)的 "role deletion" 权限并且禁用它,因此没有人可以在应用程序中删除角色.
> 提供: 更好的方式应该检查 `GetPermissionOrNull` 返回值,如果权限未定义,它会返回null值.
## IAuthorizationService ## IAuthorizationService
ASP.NET Core 提供了 `IAuthorizationService` 用于检查权限. 注入后使用它进行条件控制权限. ASP.NET Core 提供了 `IAuthorizationService` 用于检查权限. 注入后使用它进行条件控制权限.
@ -340,10 +366,10 @@ public class SystemAdminPermissionValueProvider : PermissionValueProvider
* `PermissionGrantResult.Prohibited` 禁止授权用户,任何一个授权值提供程序返回了 `Prohibited`, 那么其他的提供程序返回的值都不再重要. * `PermissionGrantResult.Prohibited` 禁止授权用户,任何一个授权值提供程序返回了 `Prohibited`, 那么其他的提供程序返回的值都不再重要.
* `PermissionGrantResult.Undefined` 代表当前无法确定是否授予或禁止权限, 返回`UnDefined`由其他权限值提供程序检查权限. * `PermissionGrantResult.Undefined` 代表当前无法确定是否授予或禁止权限, 返回`UnDefined`由其他权限值提供程序检查权限.
定义`Provider`后将其添加到 `PermissionOptions`,如下所示: 定义`Provider`后将其添加到 `AbpPermissionOptions`,如下所示:
````csharp ````csharp
Configure<PermissionOptions>(options => Configure<AbpPermissionOptions>(options =>
{ {
options.ValueProviders.Add<SystemAdminPermissionValueProvider>(); options.ValueProviders.Add<SystemAdminPermissionValueProvider>();
}); });

3
docs/zh-Hans/AutoMapper-Integration.md

@ -1,3 +0,0 @@
## AutoMapper Integration
TODO

43
docs/zh-Hans/Background-Jobs-Hangfire.md

@ -40,4 +40,45 @@ public class YourModule : AbpModule
## 配置 ## 配置
TODO... 你可以安装任何Hangfire存储. 最常用的是SQL Server(参阅[Hangfire.SqlServer](https://www.nuget.org/packages/Hangfire.SqlServer)NuGet包).
当你安装NuGet包后,你需要为你的项目配置Hangfire.
1.首先, 我们需要更改 `Module` 类 (例如: `<YourProjectName>HttpApiHostModule`) 的 `ConfigureServices` 方法去配置Hangfire存储和连接字符串:
````csharp
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
var hostingEnvironment = context.Services.GetHostingEnvironment();
//... other configarations.
ConfigureHangfire(context, configuration);
}
private void ConfigureHangfire(ServiceConfigurationContext context, IConfiguration configuration)
{
context.Services.AddHangfire(config =>
{
config.UseSqlServerStorage(configuration.GetConnectionString("Default"));
});
}
````
1. 我们需要在 `Module` 类的 `OnApplicationInitialization` 方法添加 `UseHangfireServer` 方法调用.
如果你想要使用Hangfire的面板,你可以使用: `UseHangfireDashboard`
````csharp
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
// ... others
app.UseHangfireServer();
app.UseHangfireDashboard();
}
````

18
docs/zh-Hans/Best-Practices/Application-Services.md

@ -17,7 +17,7 @@
##### 基础DTO ##### 基础DTO
**推荐** 为实体定义一个**基础**DTO. **推荐** 为聚合根定义一个**基础**DTO.
- 直接包含实体中所有的**原始属性**. - 直接包含实体中所有的**原始属性**.
- 例外: 出于**安全**原因,可以**排除**某些属性(像 `User.Password`). - 例外: 出于**安全**原因,可以**排除**某些属性(像 `User.Password`).
@ -27,7 +27,7 @@
```c# ```c#
[Serializable] [Serializable]
public class IssueDto : FullAuditedEntityDto<Guid> public class IssueDto : ExtensibleFullAuditedEntityDto<Guid>
{ {
public string Title { get; set; } public string Title { get; set; }
public string Text { get; set; } public string Text { get; set; }
@ -57,7 +57,7 @@ public class IssueLabelDto
````C# ````C#
[Serializable] [Serializable]
public class IssueWithDetailsDto : FullAuditedEntityDto<Guid> public class IssueWithDetailsDto : ExtensibleFullAuditedEntityDto<Guid>
{ {
public string Title { get; set; } public string Title { get; set; }
public string Text { get; set; } public string Text { get; set; }
@ -66,14 +66,14 @@ public class IssueWithDetailsDto : FullAuditedEntityDto<Guid>
} }
[Serializable] [Serializable]
public class MilestoneDto : EntityDto<Guid> public class MilestoneDto : ExtensibleEntityDto<Guid>
{ {
public string Name { get; set; } public string Name { get; set; }
public bool IsClosed { get; set; } public bool IsClosed { get; set; }
} }
[Serializable] [Serializable]
public class LabelDto : EntityDto<Guid> public class LabelDto : ExtensibleEntityDto<Guid>
{ {
public string Name { get; set; } public string Name { get; set; }
public string Color { get; set; } public string Color { get; set; }
@ -120,6 +120,7 @@ Task<List<QuestionWithDetailsDto>> GetListAsync(QuestionListQueryDto queryDto);
* **推荐** 使用 `CreateAsync` 做为**方法名**. * **推荐** 使用 `CreateAsync` 做为**方法名**.
* **推荐** 使用**专门的输入DTO**来创建实体. * **推荐** 使用**专门的输入DTO**来创建实体.
* **推荐** DTO类从 `ExtensibleObject` 类继承(或任何实现 `ExtensibleObject`的类) 以允许在需要时传递额外的属性.
* **推荐** 使用 **data annotations** 进行输入验证. * **推荐** 使用 **data annotations** 进行输入验证.
* 尽可能在**领域**之间共享常量(通过**domain shared** package定义的常量). * 尽可能在**领域**之间共享常量(通过**domain shared** package定义的常量).
* **推荐** 只需要创建实体的**最少**信息, 但是提供了其他可选属性. * **推荐** 只需要创建实体的**最少**信息, 但是提供了其他可选属性.
@ -134,7 +135,7 @@ Task<QuestionWithDetailsDto> CreateAsync(CreateQuestionDto questionDto);
````C# ````C#
[Serializable] [Serializable]
public class CreateQuestionDto public class CreateQuestionDto : ExtensibleObject
{ {
[Required] [Required]
[StringLength(QuestionConsts.MaxTitleLength, MinimumLength = QuestionConsts.MinTitleLength)] [StringLength(QuestionConsts.MaxTitleLength, MinimumLength = QuestionConsts.MinTitleLength)]
@ -151,6 +152,7 @@ public class CreateQuestionDto
- **推荐** 使用 `UpdateAsync` 做为**方法名**. - **推荐** 使用 `UpdateAsync` 做为**方法名**.
- **推荐** 使用**专门的输入DTO**来更新实体. - **推荐** 使用**专门的输入DTO**来更新实体.
- **推荐** DTO类从 `ExtensibleObject` 类继承(或任何实现 `ExtensibleObject`的类) 以允许在需要时传递额外的属性.
- **推荐** 获取实体的id做为分离的原始参数. 不要包含更新DTO. - **推荐** 获取实体的id做为分离的原始参数. 不要包含更新DTO.
- **推荐** 使用 **data annotations** 进行输入验证. - **推荐** 使用 **data annotations** 进行输入验证.
- 尽可能在**领域**之间共享常量(通过**domain shared** package定义的常量). - 尽可能在**领域**之间共享常量(通过**domain shared** package定义的常量).
@ -199,6 +201,10 @@ Task<int> VoteAsync(Guid id, VoteType type);
* **不推荐** 在应用程序服务方法中使用linq/sql查询来自数据库的数据. 让仓储负责从数据源执行linq/sql查询. * **不推荐** 在应用程序服务方法中使用linq/sql查询来自数据库的数据. 让仓储负责从数据源执行linq/sql查询.
#### 额外的属性
* **推荐** 使用 `MapExtraPropertiesTo` 扩展方法 ([参阅](Object-Extensions.md)) 或配置对象映射 (`MapExtraProperties`) 以允许应用开发人员能够扩展对象和服务.
#### 操作/删除 实体 #### 操作/删除 实体
* **推荐** 总是从数据库中获取所有的相关实体以对他们执行操作. * **推荐** 总是从数据库中获取所有的相关实体以对他们执行操作.

1
docs/zh-Hans/Best-Practices/Data-Transfer-Objects.md

@ -2,6 +2,7 @@
* **推荐** 在 **application.contracts** 层中定义DTO. * **推荐** 在 **application.contracts** 层中定义DTO.
* **推荐** 在可能和必要的情况下从预构建的 **基础DTO类** 继承 (如 `EntityDto<TKey>`, `CreationAuditedEntityDto<TKey>`, `AuditedEntityDto<TKey>`, `FullAuditedEntityDto<TKey>` 等). * **推荐** 在可能和必要的情况下从预构建的 **基础DTO类** 继承 (如 `EntityDto<TKey>`, `CreationAuditedEntityDto<TKey>`, `AuditedEntityDto<TKey>`, `FullAuditedEntityDto<TKey>` 等).
* **推荐** 从**聚合根**的**扩展DTO**继承(如 `ExtensibleAuditedEntityDto<TKey>`), 因为聚合根是可扩展的额外的属性使用这种方式映射到DTO.
* **推荐** 定义 **public getter 和 setter** 的DTO成员 . * **推荐** 定义 **public getter 和 setter** 的DTO成员 .
* **推荐** 使用 **data annotations** **验证** service输入DTO的属性. * **推荐** 使用 **data annotations** **验证** service输入DTO的属性.
* **不推荐** 在DTO中添加任何 **逻辑**, 在必要的时候可以实现 `IValidatableObject` 接口. * **不推荐** 在DTO中添加任何 **逻辑**, 在必要的时候可以实现 `IValidatableObject` 接口.

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

@ -106,10 +106,10 @@ public static class IdentityDbContextModelBuilderExtensions
```` ````
* **推荐** 为每个Enttiy映射调用 `b.ConfigureByConvention();`(如上所示). * **推荐** 为每个Enttiy映射调用 `b.ConfigureByConvention();`(如上所示).
* **推荐** 通过继承 `ModelBuilderConfigurationOptions` 来创建 **configuration Options** 类. 例如: * **推荐** 通过继承 `AbpModelBuilderConfigurationOptions` 来创建 **configuration Options** 类. 例如:
````C# ````C#
public class IdentityModelBuilderConfigurationOptions : ModelBuilderConfigurationOptions public class IdentityModelBuilderConfigurationOptions : AbpModelBuilderConfigurationOptions
{ {
public IdentityModelBuilderConfigurationOptions() public IdentityModelBuilderConfigurationOptions()
: base(AbpIdentityConsts.DefaultDbTablePrefix, AbpIdentityConsts.DefaultDbSchema) : base(AbpIdentityConsts.DefaultDbTablePrefix, AbpIdentityConsts.DefaultDbSchema)

6
docs/zh-Hans/Best-Practices/MongoDB-Integration.md

@ -1,4 +1,4 @@
## MongoDB 集成 ## MongoDB 集成
* **推荐** 为每个模块定义一个独立的 `MongoDbContext` 接口与实现类. * **推荐** 为每个模块定义一个独立的 `MongoDbContext` 接口与实现类.
@ -90,11 +90,11 @@ public static class AbpIdentityMongoDbContextExtensions
} }
``` ```
- **推荐** 通过继承 `MongoModelBuilderConfigurationOptions` 来创建 **configuration Options** 类. 示例: - **推荐** 通过继承 `AbpMongoModelBuilderConfigurationOptions` 来创建 **configuration Options** 类. 示例:
```c# ```c#
public class IdentityMongoModelBuilderConfigurationOptions public class IdentityMongoModelBuilderConfigurationOptions
: MongoModelBuilderConfigurationOptions : AbpMongoModelBuilderConfigurationOptions
{ {
public IdentityMongoModelBuilderConfigurationOptions() public IdentityMongoModelBuilderConfigurationOptions()
: base(AbpIdentityConsts.DefaultDbTablePrefix) : base(AbpIdentityConsts.DefaultDbTablePrefix)

2
docs/zh-Hans/CLI.md

@ -60,6 +60,8 @@ abp new Acme.BookStore
* `--version` 或者 `-v`: 指定ABP和模板的版本.它可以是 [release tag](https://github.com/abpframework/abp/releases) 或者 [branch name](https://github.com/abpframework/abp/branches). 如果没有指定,则使用最新版本.大多数情况下,您会希望使用最新的版本. * `--version` 或者 `-v`: 指定ABP和模板的版本.它可以是 [release tag](https://github.com/abpframework/abp/releases) 或者 [branch name](https://github.com/abpframework/abp/branches). 如果没有指定,则使用最新版本.大多数情况下,您会希望使用最新的版本.
* `--template-source` 或者 `-ts`: 指定自定义模板源用于生成项目,可以使用本地源和网络源(例如 `D\localTemplate` 或 `https://<your url>.zip`). * `--template-source` 或者 `-ts`: 指定自定义模板源用于生成项目,可以使用本地源和网络源(例如 `D\localTemplate` 或 `https://<your url>.zip`).
* `--create-solution-folder` 或者 `-csf`: 指定项目是在输出文件夹中的新文件夹中还是直接在输出文件夹中. * `--create-solution-folder` 或者 `-csf`: 指定项目是在输出文件夹中的新文件夹中还是直接在输出文件夹中.
* `--connection-string` 或者 `-cs`: 重写所有 `appsettings.json` 文件的默认连接字符串. 默认连接字符串是 `Server=localhost;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true`. 如果你不想使用默认,你可以设置自己的连接字符串. 默认的数据库提供程序是 `SQL Server`, 所以你只能输入SQL Server连接字符串!
* `--local-framework-ref --abp-path`: 使用对项目的本地引用,而不是替换为NuGet包引用.
### add-package ### add-package

4
docs/zh-Hans/Contribution/Localization-Text-Files.md

@ -23,8 +23,8 @@
* https://github.com/abpframework/abp/tree/master/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json * https://github.com/abpframework/abp/tree/master/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json
* https://github.com/abpframework/abp/tree/master/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/en.json * https://github.com/abpframework/abp/tree/master/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/en.json
* https://github.com/abpframework/abp/tree/master/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en.json * https://github.com/abpframework/abp/tree/master/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en.json
* https://github.com/abpframework/abp/tree/master/samples/BookStore/src/Acme.BookStore.Domain.Shared/Localization/BookStore/en.json * https://github.com/abpframework/abp-samples/tree/master/BookStore/src/Acme.BookStore.Domain.Shared/Localization/BookStore/en.json
* https://github.com/abpframework/abp/tree/master/samples/DashboardDemo/src/DashboardDemo.Domain.Shared/Localization/DashboardDemo/en.json * https://github.com/abpframework/abp-samples/tree/master/DashboardDemo/src/DashboardDemo.Domain.Shared/Localization/DashboardDemo/en.json
* https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/en.json * https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/en.json
* https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/en.json * https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/en.json
* https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/en.json * https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/en.json

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

@ -37,13 +37,14 @@ return user.GetProperty<string>("Title");
为了解决上面的问题,用于EF Core的ABP框架实体扩展系统允许你使用上面定义相同的额外属性API,但将所需的属性存储在单独的数据库表字段中. 为了解决上面的问题,用于EF Core的ABP框架实体扩展系统允许你使用上面定义相同的额外属性API,但将所需的属性存储在单独的数据库表字段中.
假设你想要添加 `SocialSecurityNumber` 到[身份模块](Modules/Identity.md)的 `IdentityUser` 实体. 你可以使用 `EntityExtensionManager` 静态类: 假设你想要添加 `SocialSecurityNumber` 到[身份模块](Modules/Identity.md)的 `IdentityUser` 实体. 你可以使用 `ObjectExtensionManager` 类:
````csharp ````csharp
EntityExtensionManager.AddProperty<IdentityUser, string>( ObjectExtensionManager.Instance
"SocialSecurityNumber", .MapEfCoreProperty<IdentityUser, string>(
b => { b.HasMaxLength(32); } "SocialSecurityNumber",
); b => { b.HasMaxLength(32); }
);
```` ````
* 你提供了 `IdentityUser` 作为实体名(泛型参数), `string` 做为新属性的类型, `SocialSecurityNumber` 做为属性名(也是数据库表的字段名). * 你提供了 `IdentityUser` 作为实体名(泛型参数), `string` 做为新属性的类型, `SocialSecurityNumber` 做为属性名(也是数据库表的字段名).
@ -57,8 +58,6 @@ EntityExtensionManager.AddProperty<IdentityUser, string>(
## 创建新实体映射到同一个数据库表/Collection ## 创建新实体映射到同一个数据库表/Collection
尽管额外属性方法**易于使用**并且适用于一些场景,但它具有[实体文档](Entities.md)中描述的一些缺点.
另一个方法是**创建你自己的实体**映射到**同一个数据库库**(对于MongoDB数据库是collection) 另一个方法是**创建你自己的实体**映射到**同一个数据库库**(对于MongoDB数据库是collection)
[应用程序启动模板](Startup-Templates/Application.md)的 `AppUser` 已经实现了这种方法. [EF Core迁移文档](Entity-Framework-Core-Migrations.md)描述了在这些情况下如何实现和管理**EF Core数据库迁移**. 这种方法同样适用于MongoDB,但你不需要处理数据库迁移问题. [应用程序启动模板](Startup-Templates/Application.md)的 `AppUser` 已经实现了这种方法. [EF Core迁移文档](Entity-Framework-Core-Migrations.md)描述了在这些情况下如何实现和管理**EF Core数据库迁移**. 这种方法同样适用于MongoDB,但你不需要处理数据库迁移问题.

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

@ -161,6 +161,105 @@ public class MyIdentityUserManager : IdentityUserManager
控制器,框架服务,视图组件类以及其他类型注册到依赖注入的类都可以像上面的示例那样被重写. 控制器,框架服务,视图组件类以及其他类型注册到依赖注入的类都可以像上面的示例那样被重写.
## 扩展数据传输对象
你可以如[扩展实体文档](Customizing-Application-Modules-Extending-Entities.md)所述扩展实体. 并使用上面介绍的重写相关服务**使用自定义属性****执行其他业务逻辑**.
应用程序使用的数据传输对象(**DTO**)同样可扩展. 这样你可以使服务返回其他属性并在UI(或其他客户端)得到其他属性.
### 示例
假设你已经按照[扩展实体文档](Customizing-Application-Modules-Extending-Entities.md)中的说明添加了 `SocialSecurityNumber` 并希望从 `IdentityUserAppService的GetListAsync` 方法获取用户列表时包括此属性.
你可以使用[对象扩展系统](Object-Extensions.md)将属性添加到 `IdentityUserDto`. 在应用程序启动模板带有的 `YourProjectNameDtoExtensions` 类中编写以下代码:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUserDto, string>(
"SocialSecurityNumber"
);
````
这段代码为 `IdentityUserDto` 类添加了 `string` 类型的 `SocialSecurityNumber` 属性. 现在你可以在RREST API客户端调用 `/api/identity/users` HTTP API(内部使用 `IdentityUserAppService`),你会在 `extraProperties` 部分看到 `SocialSecurityNumber` 值.
````json
{
"totalCount": 1,
"items": [{
"tenantId": null,
"userName": "admin",
"name": "admin",
"surname": null,
"email": "admin@abp.io",
"emailConfirmed": false,
"phoneNumber": null,
"phoneNumberConfirmed": false,
"twoFactorEnabled": false,
"lockoutEnabled": true,
"lockoutEnd": null,
"concurrencyStamp": "b4c371a0ab604de28af472fa79c3b70c",
"isDeleted": false,
"deleterId": null,
"deletionTime": null,
"lastModificationTime": "2020-04-09T21:25:47.0740706",
"lastModifierId": null,
"creationTime": "2020-04-09T21:25:46.8308744",
"creatorId": null,
"id": "8edecb8f-1894-a9b1-833b-39f4725db2a3",
"extraProperties": {
"SocialSecurityNumber": "123456789"
}
}]
}
````
手动添加了 `123456789` 值到数据库中.
所有预构建的模块都在DTO中支持额外属性,你可以对其轻松的配置.
### 定义检查
当为实体[定义](Customizing-Application-Modules-Extending-Entities.md)额外的属性时,由于安全性它不会自动出现在所有相关的DTO中. 额外属性可能包含敏感数据并且你可能不想默认公开给客户端.
因此如果要用于DTO,需要为相应的DTO显式定义相同的属性(如上所述). 如果要允许在用户创建时进行设置还需要为 `IdentityUserCreateDto` 定义.
如果属性并不是安全敏感,这可能会很枯燥. 对象扩展系统允许你忽略检查定义的属性. 参阅示例:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, string>(
"SocialSecurityNumber",
options =>
{
options.MapEfCore(b => b.HasMaxLength(32));
options.CheckPairDefinitionOnMapping = false;
}
);
````
这是定义实体属性的另一种方法( 有关 `ObjectExtensionManager` 更多信息,请参阅[文档](Object-Extensions.md)). 这次我们设置了 `CheckPairDefinitionOnMapping` 为false,在将实体映射到DTO时会跳过定义检查.
如果你不喜欢这种方法,但想简单的向多个对象(DTO)添加单个属, `AddOrUpdateProperty` 可以使用类型数组添加额外的属性:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<string>(
new[]
{
typeof(IdentityUserDto),
typeof(IdentityUserCreateDto),
typeof(IdentityUserUpdateDto)
},
"SocialSecurityNumber"
);
````
### 关于用户界面
该系统允许你向实体和DTO添加额外的属性并执行自定义业务代码,但它与用户界面无关.
参阅 [重写用户界面](Customizing-Application-Modules-Overriding-User-Interface.md) 指南了解关于UI部分.
## 如何找到服务? ## 如何找到服务?
[模块文档](Modules/Index.md) 包含了定义的主要服务列表. 另外 你也可以查看[源码](https://github.com/abpframework/abp/tree/dev/modules)找到所有的服务. [模块文档](Modules/Index.md) 包含了定义的主要服务列表. 另外 你也可以查看[源码](https://github.com/abpframework/abp/tree/dev/modules)找到所有的服务.

2
docs/zh-Hans/Entities.md

@ -367,7 +367,7 @@ public static class IdentityUserExtensions
* 对于 [Entity Framework Core](Entity-Framework-Core.md),这是两种类型的配置; * 对于 [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)系统自动完成. * 默认它以 `JSON` 字符串形式存储在 `ExtraProperties` 字段中. 序列化到 `JSON` 和反序列化到 `JSON` 由ABP使用EF Core的[值转换](https://docs.microsoft.com/zh-cn/ef/core/modeling/value-conversions)系统自动完成.
* 如果需要,你可以使用 `EntityExtensionManager` 为所需的额外属性定义一个单独的数据库字段. 那些使用 `EntityExtensionManager` 配置的属性继续使用单个 `JSON` 字段. 当你使用预构建的[应用模块](Modules/Index.md)并且想要[扩展模块的实体](Customizing-Application-Modules-Extending-Entities.md). 参阅[EF Core迁移文档](Entity-Framework-Core.md)了解如何使用 `EntityExtensionManager`. * 如果需要,你可以使用 `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) 系统.
### 讨论额外的属性 ### 讨论额外的属性

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

@ -7,7 +7,7 @@
### 源码 ### 源码
你可以在[这里](https://github.com/abpframework/abp/tree/dev/samples/EfCoreMigrationDemo)找到本文引用的示例项目的源代码; 但是为了理解示例项目的源代码,你需要阅读和理解这个文档. 你可以在[这里](https://github.com/abpframework/abp-samples/tree/master/EfCoreMigrationDemo)找到本文引用的示例项目的源代码; 但是为了理解示例项目的源代码,你需要阅读和理解这个文档.
## 关于EF Core 代码优先迁移 ## 关于EF Core 代码优先迁移
@ -411,10 +411,11 @@ public static class MyProjectNameEntityExtensions
{ {
OneTimeRunner.Run(() => OneTimeRunner.Run(() =>
{ {
EntityExtensionManager.AddProperty<IdentityRole, string>( ObjectExtensionManager.Instance
"Title", .MapEfCoreProperty<IdentityRole, string>(
b => { b.HasMaxLength(128); } "Title",
); builder => { builder.HasMaxLength(64); }
);
}); });
} }
} }
@ -422,7 +423,7 @@ public static class MyProjectNameEntityExtensions
> 我们建议使用 `nameof(AppRole.Title)` 而不是硬编码 "Title" 字符串 > 我们建议使用 `nameof(AppRole.Title)` 而不是硬编码 "Title" 字符串
`EntityExtensionManager` 用于添加属性到现有的实体. 由于 `EntityExtensionManager` 是静态的,因此应调用一次. `OneTimeRunner` 是ABP框架定义简单的工具类. `ObjectExtensionManager` 用于添加属性到现有的实体. 由于 `ObjectExtensionManager.Instance` 是静态实例(单例),因此应调用一次. `OneTimeRunner` 是ABP框架定义简单的工具类.
参阅[EF Core集成文档](Entity-Framework-Core.md)了解更多关于实体扩展系统. 参阅[EF Core集成文档](Entity-Framework-Core.md)了解更多关于实体扩展系统.
@ -547,7 +548,7 @@ public class IdentityRoleExtendingService : ITransientDependency
实体扩展系统解决了额外属性主要的问题: 它可以将额外属性做为**标准表字段**存储到数据库. 实体扩展系统解决了额外属性主要的问题: 它可以将额外属性做为**标准表字段**存储到数据库.
你需要做的就是如上所诉使用 `EntityExtensionManager` 定义额外属性, 然后你就可以使得 `GetProperty` 和 `SetProperty` 方法对实体的属性进行get/set,但是这时它存储在数据库表的单独字段中. 你需要做的就是如上所诉使用 `ObjectExtensionManager` 定义额外属性, 然后你就可以使得 `GetProperty` 和 `SetProperty` 方法对实体的属性进行get/set,但是这时它存储在数据库表的单独字段中.
###### 创建新表 ###### 创建新表

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

@ -298,56 +298,57 @@ public class BookService
> 要点: 你必须在使用`DbContext`的项目里引用`Volo.Abp.EntityFrameworkCore`包. 这会破坏封装,但在这种情况下,这就是你需要的. > 要点: 你必须在使用`DbContext`的项目里引用`Volo.Abp.EntityFrameworkCore`包. 这会破坏封装,但在这种情况下,这就是你需要的.
## Extra Properties & Entity Extension Manager ## Extra Properties & Object Extension Manager
额外属性系统允许你为实现了 `IHasExtraProperties` 的实体set/get动态属性. 当你想将自定义属性添加到[应用程序模块](Modules/Index.md)中定义的实体时,它特别有用. 额外属性系统允许你为实现了 `IHasExtraProperties` 的实体set/get动态属性. 当你想将自定义属性添加到[应用程序模块](Modules/Index.md)中定义的实体时,它特别有用.
默认,实体的所有额外属性存储在数据库的一个 `JSON` 对象中. 实体扩展系统允许你存储额外属性在数据库的单独字段中. 默认,实体的所有额外属性存储在数据库的一个 `JSON` 对象中.
有关额外属性和实体扩展系统的更多信息,请参阅下列文档: 实体扩展系统允许你存储额外属性在数据库的单独字段中. 有关额外属性和实体扩展系统的更多信息,请参阅下列文档:
* [自定义应用模块: 扩展实体](Customizing-Application-Modules-Extending-Entities.md) * [自定义应用模块: 扩展实体](Customizing-Application-Modules-Extending-Entities.md)
* [实体](Entities.md) * [实体](Entities.md)
本节只解释了 `EntityExtensionManager` 及其用法. 本节只解释了 EF Core相关的 `ObjectExtensionManager` 及其用法.
### AddProperty 方法 ### ObjectExtensionManager.Instance
`EntityExtensionManager` 的 `AddProperty` 方法允许你实体定义附加的属性. `ObjectExtensionManager` 实现单例模式,因此你需要使用静态的 `ObjectExtensionManager.Instance` 来执行所有操作。
### MapEfCoreProperty
`MapEfCoreProperty` 是一种快捷扩展方法,用于定义实体的扩展属性并映射到数据库.
**示例**: 添加 `Title` 属性 (数据库字段)到 `IdentityRole` 实体: **示例**: 添加 `Title` 属性 (数据库字段)到 `IdentityRole` 实体:
````csharp ````csharp
EntityExtensionManager.AddProperty<IdentityRole, string>( ObjectExtensionManager.Instance
"Title", .MapEfCoreProperty<IdentityRole, string>(
b => { b.HasMaxLength(128); } "Title",
); builder => { builder.HasMaxLength(64); }
);
```` ````
如果相关模块已实现此功能(通过使用下面说明的 `ConfigureExtensions`)则将新属性添加到模型中. 然后你需要运行标准的 `Add-Migration` 和 `Update-Database` 命令更新数据库以添加新字段. 如果相关模块已实现此功能(通过使用下面说明的 `ConfigureEfCoreEntity`)则将新属性添加到模型中. 然后你需要运行标准的 `Add-Migration` 和 `Update-Database` 命令更新数据库以添加新字段.
>`AddProperty` 方法必须在使用相关的 `DbContext` 之前调用,它是一个静态方法. 最好的方法是尽早的应用程序中使用它. 应用程序启动模板含有 `YourProjectNameEntityExtensions` 类,可以在放心的在此类中使用此方法. >`MapEfCoreProperty` 方法必须在使用相关的 `DbContext` 之前调用,它是一个静态方法. 最好的方法是尽早的应用程序中使用它. 应用程序启动模板含有 `YourProjectNameEntityExtensions` 类,可以在放心的在此类中使用此方法.
### ConfigureExtensions ### ConfigureEfCoreEntity
如果你正在开发一个可重用使用的模块,并允许应用程序开发人员将属性添加到你的实体,你可以在实体映射使用 `ConfigureExtensions` 扩展方法: 如果你正在开发一个可重用使用的模块,并允许应用程序开发人员将属性添加到你的实体,你可以在实体映射使用 `ConfigureEfCoreEntity` 扩展方法,但是在配置实体映射时可以使用快捷的扩展方法 `ConfigureObjectExtensions`:
````csharp ````csharp
builder.Entity<YourEntity>(b => builder.Entity<YourEntity>(b =>
{ {
b.ConfigureExtensions(); b.ConfigureObjectExtensions();
//... //...
}); });
```` ````
如果你调用 `ConfigureByConvention()` 扩展方法(在此示例中 `b.ConfigureByConvention`),ABP框架内部会调用 `ConfigureExtensions` 方法. 使用 `ConfigureByConvention` 方法是**最佳实践**,因为它还按照约定配置基本属性的数据库映射. 如果你调用 `ConfigureByConvention()` 扩展方法(在此示例中 `b.ConfigureByConvention`),ABP框架内部会调用 `ConfigureObjectExtensions` 方法. 使用 `ConfigureByConvention` 方法是**最佳实践**,因为它还按照约定配置基本属性的数据库映射.
参阅上面提到的 "*ConfigureByConvention 方法*" 了解更多信息. 参阅上面提到的 "*ConfigureByConvention 方法*" 了解更多信息.
### GetPropertyNames
`EntityExtensionManager.GetPropertyNames` 静态方法可以用作为此实体定义的扩展属性的名称. 应用程序代码通常不需要,但是ABP框架在内部使用它.
## 高级主题 ## 高级主题
### 设置默认仓储类 ### 设置默认仓储类

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

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

2
docs/zh-Hans/Getting-Started-AspNetCore-Application.md

@ -186,4 +186,4 @@ public class Program
### 源码 ### 源码
从[此处](https://github.com/abpframework/abp/tree/dev/samples/BasicAspNetCoreApplication)获取本教程中创建的示例项目的源代码. 从[此处](https://github.com/abpframework/abp-samples/tree/master/BasicAspNetCoreApplication)获取本教程中创建的示例项目的源代码.

2
docs/zh-Hans/Getting-Started-Console-Application.md

@ -121,4 +121,4 @@ namespace AbpConsoleDemo
### 源码 ### 源码
从[这里](https://github.com/abpframework/abp/tree/dev/samples/BasicConsoleApplication)获取本教程中创建的示例项目的源代码. 从[这里](https://github.com/abpframework/abp-samples/tree/master/BasicConsoleApplication)获取本教程中创建的示例项目的源代码.

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)

198
docs/zh-Hans/How-To/Azure-Active-Directory-Authentication-MVC.md

@ -0,0 +1,198 @@
# 如何对MVC / Razor页面应用程序使用Azure Active Directory身份验证
本文介绍了如何将AzureAD集成到ABP应用程序中,用 **Azure Active Directory** 凭据使用 OAuth 2.0 登录.
添加Azure Active Directory到ABP框架非常简单,只需要正确的完成几个配置.
为了覆盖更多范围,我们演示两种不同的集成AzureAD的**方法**.
1. **AddAzureAD**: 该方法使用微软[AzureAD UI nuget 包](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.AzureAD.UI/),在网络上搜索如何将AzureAD集成到应用程序时,这个包是最流行的.
2. **AddOpenIdConnect**: 该方法使用默认的[OpenIdConnect](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.OpenIdConnect/). 它不仅可用于AzureAD,还可用于所有OpenId连接.
> 这些方法之间的功能**没有区别**,AddAzureAD是具有预定义Cookie设置的OpenIdConnection([源](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADAuthenticationBuilderExtensions.cs#L122))的抽象方法.
>
> 但是默认配置的登录方案在与ABP应用程序集成方面存在关键差异,下面将对此进行说明.
## 1. AddAzureAD
这个方法使用 [Microsoft AzureAD UI nuget 包](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.AzureAD.UI/),它是最常用的集成AzureAD方法.
如果选择这种方法,需要将 `Microsoft.AspNetCore.Authentication.AzureAD.UI` 软件包安装到 **.Web** 项目中. 由于AddAzureAD扩展使用[配置绑定](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.1#default-configuration),你需要更改 **.Web** 项目中的appsettings.json文件.
#### **更改 `appsettings.json`**
你添加向 `appsettings.json` 添加新的配置节,在配置 `OpenIdConnectOptions` 时绑定配置:
````json
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "<your-tenant-id>",
"ClientId": "<your-client-id>",
"Domain": "domain.onmicrosoft.com",
"CallbackPath": "/signin-azuread-oidc"
}
````
> 这里重要的配置是CallbackPath. 值必须与你的 Azure AD-> app registrations-> Authentication -> RedirectUri 之一相同.
然后你需要配置 `OpenIdConnectOptions` 完成集成.
#### 配置 OpenIdConnectOptions
在你的 **.Web** 项目找到 **ApplicationWebModule** 使用以下代码修改 `ConfigureAuthentication` 方法:
````csharp
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier);
context.Services.AddAuthentication()
.AddIdentityServerAuthentication(options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = false;
options.ApiName = "Acme.BookStore";
})
.AddAzureAD(options => configuration.Bind("AzureAd", options));
context.Services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.Authority = options.Authority + "/v2.0/";
options.ClientId = configuration["AzureAd:ClientId"];
options.CallbackPath = configuration["AzureAd:CallbackPath"];
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters.ValidateIssuer = false;
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
options.SignInScheme = IdentityConstants.ExternalScheme;
options.Scope.Add("email");
});
}
````
> **不要忘记:**
>
> * 在 `AddAuthentication()` 之后添加 `.AddAzureAD(options => configuration.Bind("AzureAd", options))` . 它绑定了你的 AzureAD 配置并且容易忘记.
> * 添加 `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear()`. 它会禁用默认的 Microsoft claim type 映射.
> * 添加 `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier)`. 映射 [ClaimTypes.NameIdentifier](https://github.com/dotnet/runtime/blob/6d395de48ac718a913e567ae80961050f2a9a4fa/src/libraries/System.Security.Claims/src/System/Security/Claims/ClaimTypes.cs#L59) 很重要,因为默认SignIn Manager和行为使用这个claim type用于外部登录信息.
> * 添加 `options.SignInScheme = IdentityConstants.ExternalScheme` 因为 [默认登录方法为 `AzureADOpenID`](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADOpenIdConnectOptionsConfiguration.cs#L35).
> * 如果你使用的是 **v2.0** 端点,应添加 `options.Scope.Add("email")` 因为 v2.0 端点不会将 `email` 做为默认值返回. [账户模块](../Modules/Account.md) 使用 `email` claim 来 [注册外部账户](https://github.com/abpframework/abp/blob/be32a55449e270d2d456df3dabdc91f3ffdd4fa9/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs#L215).
你已经完成了集成.
## 2. 替代方法: AddOpenIdConnect
如果你不想在应用程序安装一个额外的NuGet包,你可以使用默认的[OpenIdConnect](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.OpenIdConnect/),它适用于所有的OpenId连接,包括AzureAD外部认证.
你不必使用 `appsettings.json` 配置, 但将AzureAD信息放在 `appsettings.json` 是一个很好的做法.
为了从 `appsettings.json` 获取AzureAD信息在 `OpenIdConnectOptions` 配置使用,只需要在你的 **.Web** 项目中的 `appsettings.json` 添加一个新的配置节:
````json
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "<your-tenant-id>",
"ClientId": "<your-client-id>",
"Domain": "domain.onmicrosoft.com",
"CallbackPath": "/signin-azuread-oidc"
}
````
然后在你的 **.Web** 项目的 **ApplicationWebModule** 用以下代码修改 `ConfigureAuthentication` 方法:
````csharp
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier);
context.Services.AddAuthentication()
.AddIdentityServerAuthentication(options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = false;
options.ApiName = "BookStore";
})
.AddOpenIdConnect("AzureOpenId", "Azure Active Directory OpenId", options =>
{
options.Authority = "https://login.microsoftonline.com/" + configuration["AzureAd:TenantId"] + "/v2.0/";
options.ClientId = configuration["AzureAd:ClientId"];
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.CallbackPath = configuration["AzureAd:CallbackPath"];
options.RequireHttpsMetadata = false;
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("email");
});
}
````
集成结束. 请记住你可以连接任何其他外部认证供应商.
## 本文的源代码
你可以在[这里](https://github.com/abpframework/abp-samples/tree/master/aspnet-core/Authentication-Customization)找到已完成的示例源码.
# FAQ
* Help! `GetExternalLoginInfoAsync` 返回 `null`!
* 有两方面的原因;
1. 你在尝试验证错误的方案. 检查是否设置 **SignInScheme** 为 `IdentityConstants.ExternalScheme`:
````csharp
options.SignInScheme = IdentityConstants.ExternalScheme;
````
2. 你的 `ClaimTypes.NameIdentifier` 为 `null`. 检查是否添加 claim 映射:
````csharp
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier);
````
* Help! 我一直得到 ***AADSTS50011: The reply URL specified in the request does not match the reply URLs configured for the application*** 错误!
* 如果你在appsettings设置 **CallbackPath** 为:
````csharp
"AzureAd": {
...
"CallbackPath": "/signin-azuread-oidc"
}
````
你在azure门户的应用程序**重定向URI**必须具有之类 `https://localhost:44320/signin-azuread-oidc` 的<u>域</u>, 而不仅是 `/signin-azuread-oidc`.
* Help! 我一直得到 ***System.ArgumentNullException: Value cannot be null. (Parameter 'userName')*** 错误!
* 当你使用 Azure Authority **v2.0 端点** 而不请求 `email` 域, 会发生这些情况. [Abp 创建用户检查了唯一的邮箱](https://github.com/abpframework/abp/blob/037ef9abe024c03c1f89ab6c933710bcfe3f5c93/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs#L208). 只需添加
````csharp
options.Scope.Add("email");
````
到你的 openid 配置.
* 如何**调试/监视**在映射之前获得的声明?
* 你可以在 openid 配置下加一个简单的事件在映射之前进行调试,例如:
````csharp
options.Events.OnTokenValidated = (async context =>
{
var claimsFromOidcProvider = context.Principal.Claims.ToList();
await Task.CompletedTask;
});
````
## 另请参阅
* [如何为MVC / Razor页面应用程序自定义登录页面](Customize-Login-Page-MVC.md).
* [如何为ABP应用程序定制SignIn Manager](Customize-SignIn-Manager.md).

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

@ -0,0 +1,113 @@
# 如何为MVC / Razor页面应用程序自定义登录页面
当你使用[应用程序启动模板](../Startup-Templates/Application.md)创建了一个新的应用程序, 登录页面的源代码并不在你的解决方案中,所以你不能直接更改. 它来自[账户模块](../Modules/Account.md),使用[NuGet包](https://www.nuget.org/packages/Volo.Abp.Account.Web)引用.
本文介绍了如何为自己的应用程序自定义登录页面.
## 创建登录 PageModel
创建一个新的类继承账户模块的[LoginModel](https://github.com/abpframework/abp/blob/037ef9abe024c03c1f89ab6c933710bcfe3f5c93/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs).
````csharp
public class CustomLoginModel : LoginModel
{
public CustomLoginModel(
Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider schemeProvider,
Microsoft.Extensions.Options.IOptions<Volo.Abp.Account.Web.AbpAccountOptions> accountOptions)
: base(schemeProvider, accountOptions)
{
}
}
````
> 在这里命令约定很重要. 如果你的类名不是以 `LoginModel` 结束,你需要手动在[依赖注入](../Dependency-Injection.md)系统替换 `LoginModel`.
然后你可以覆盖任何方法并添加用户界面需要的新方法和属性.
## 重写登录页面UI
在 **Pages** 目录下创建名为 **Account** 的文件夹,并在这个文件夹中创建 `Login.cshtml` ,借助[虚拟文件系统](../Virtual-File-System.md)它会自动覆盖账户模块的页面文件.
自定义页面一个很好的开始是复制它的源代码. [点击这里](https://github.com/abpframework/abp/blob/dev/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml)找到登录页面的源码. 在编写本文档时,源代码如下:
````xml
@page
@using Volo.Abp.Account.Settings
@using Volo.Abp.Settings
@model Acme.BookStore.Web.Pages.Account.CustomLoginModel
@inherits Volo.Abp.Account.Web.Pages.Account.AccountPage
@inject Volo.Abp.Settings.ISettingProvider SettingProvider
@if (Model.EnableLocalLogin)
{
<div class="card mt-3 shadow-sm rounded">
<div class="card-body p-5">
<h4>@L["Login"]</h4>
@if (await SettingProvider.IsTrueAsync(AccountSettingNames.IsSelfRegistrationEnabled))
{
<strong>
@L["AreYouANewUser"]
<a href="@Url.Page("./Register", new {returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash})" class="text-decoration-none">@L["Register"]</a>
</strong>
}
<form method="post" class="mt-4">
<input asp-for="ReturnUrl" />
<input asp-for="ReturnUrlHash" />
<div class="form-group">
<label asp-for="LoginInput.UserNameOrEmailAddress"></label>
<input asp-for="LoginInput.UserNameOrEmailAddress" class="form-control" />
<span asp-validation-for="LoginInput.UserNameOrEmailAddress" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="LoginInput.Password"></label>
<input asp-for="LoginInput.Password" class="form-control" />
<span asp-validation-for="LoginInput.Password" class="text-danger"></span>
</div>
<div class="form-check">
<label asp-for="LoginInput.RememberMe" class="form-check-label">
<input asp-for="LoginInput.RememberMe" class="form-check-input" />
@Html.DisplayNameFor(m => m.LoginInput.RememberMe)
</label>
</div>
<abp-button type="submit" button-type="Primary" name="Action" value="Login" class="btn-block btn-lg mt-3">@L["Login"]</abp-button>
</form>
</div>
<div class="card-footer text-center border-0">
<abp-button type="button" button-type="Link" name="Action" value="Cancel" class="px-2 py-0">@L["Cancel"]</abp-button> @* TODO: Only show if identity server is used *@
</div>
</div>
}
@if (Model.VisibleExternalProviders.Any())
{
<div class="col-md-6">
<h4>@L["UseAnotherServiceToLogIn"]</h4>
<form asp-page="./Login" asp-page-handler="ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" asp-route-returnUrlHash="@Model.ReturnUrlHash" method="post">
<input asp-for="ReturnUrl" />
<input asp-for="ReturnUrlHash" />
@foreach (var provider in Model.VisibleExternalProviders)
{
<button type="submit" class="btn btn-primary" name="provider" value="@provider.AuthenticationScheme" title="@L["GivenTenantIsNotAvailable", provider.DisplayName]">@provider.DisplayName</button>
}
</form>
</div>
}
@if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders.Any())
{
<div class="alert alert-warning">
<strong>@L["InvalidLoginRequest"]</strong>
@L["ThereAreNoLoginSchemesConfiguredForThisClient"]
</div>
}
````
只需更改 `@model` 为 `Acme.BookStore.Web.Pages.Account.CustomLoginModel` 使用自定义的 `PageModel` 类. 你可以做任何应用程序需要的更改.
## 本文的源代码
你可以在[这里](https://github.com/abpframework/abp-samples/tree/master/aspnet-core/Authentication-Customization)找到已完成的示例源码.
## 另请参阅
* [ASP.NET Core (MVC / Razor Pages) 用户界面自定义指南](../UI/AspNetCore/Customization-User-Interface.md).

101
docs/zh-Hans/How-To/Customize-SignIn-Manager.md

@ -0,0 +1,101 @@
# 如何为ABP应用程序定制SignIn Manager
在使用[应用程序启动模板](../Startup-Templates/Application.md)创建新项目后,你可能想要扩展或更改SignIn Manager的默认行为,以满足你需要的身份验证和注册流程. ABP[账户模块](../Modules/Account.md)使用[身份管理模块](../Modules/Identity.md)做为SignIn Manager,而[身份管理模块](../Modules/Identity.md)使用默认的[Microsoft Identity SignIn Manager](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs)([参阅此处]((https://github.com/abpframework/abp/blob/be32a55449e270d2d456df3dabdc91f3ffdd4fa9/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreModule.cs#L17))).
编写自定义SignIn Manager,你需要扩展[Microsoft Identity SignIn Manager](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs)类并注入到DI容器.
本文介绍了如何为你自己的应用程序自定义SignIn Manager.
## 创建 CustomSignInManager
创建一个类并继承自Microsoft Identity 包的 [SignInMager](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs).
````csharp
public class CustomSignInManager : Microsoft.AspNetCore.Identity.SignInManager<Volo.Abp.Identity.IdentityUser>
{
public CustomSignInManager(
Microsoft.AspNetCore.Identity.UserManager<Volo.Abp.Identity.IdentityUser> userManager,
Microsoft.AspNetCore.Http.IHttpContextAccessor contextAccessor,
Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory<Volo.Abp.Identity.IdentityUser> claimsFactory,
Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Identity.IdentityOptions> optionsAccessor,
Microsoft.Extensions.Logging.ILogger<Microsoft.AspNetCore.Identity.SignInManager<Volo.Abp.Identity.IdentityUser>> logger,
Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider schemes,
Microsoft.AspNetCore.Identity.IUserConfirmation<Volo.Abp.Identity.IdentityUser> confirmation)
: base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation)
{
}
}
````
> 重点是使用**Volo.Abp.Identity.IdentityUser**做为泛型参数,而不是应用程序的AppUser.
然后你可以覆盖SignIn Manager的任何方法并且为你的身份验证和注册流程添加需要的方法和属性.
## 重写 GetExternalLoginInfoAsync 方法
在这个用例中我们重写第三方身份验证时使用的 `GetExternalLoginInfoAsync` 方法实现.
一个好的开始是从复制[源码](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Identity/Core/src/SignInManager.cs#L638-L674)而不是从零开始. 在这个用例中我们对源码进行较少的修改,为了帮助理解概念它显式显示了方法和属性的命名空间.
````csharp
public override async Task<Microsoft.AspNetCore.Identity.ExternalLoginInfo> GetExternalLoginInfoAsync(string expectedXsrf = null)
{
var auth = await Context.AuthenticateAsync(Microsoft.AspNetCore.Identity.IdentityConstants.ExternalScheme);
var items = auth?.Properties?.Items;
if (auth?.Principal == null || items == null || !items.ContainsKey("LoginProviderKey"))
{
return null;
}
if (expectedXsrf != null)
{
if (!items.ContainsKey("XsrfKey"))
{
return null;
}
var userId = items[XsrfKey] as string;
if (userId != expectedXsrf)
{
return null;
}
}
var providerKey = auth.Principal.FindFirstValue(ClaimTypes.NameIdentifier);
var provider = items[LoginProviderKey] as string;
if (providerKey == null || provider == null)
{
return null;
}
var providerDisplayName = (await GetExternalAuthenticationSchemesAsync()).FirstOrDefault(p => p.Name == provider)?.DisplayName
?? provider;
return new Microsoft.AspNetCore.Identity.ExternalLoginInfo(auth.Principal, provider, providerKey, providerDisplayName)
{
AuthenticationTokens = auth.Properties.GetTokens()
};
}
````
要使你自定义的SignIn Manager类生效,你需要将其注册[依赖注入系统](../Dependency-Injection.md)中.
## 注册到依赖注入
应该使用 [IdentityBuilder](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Extensions.Core/src/IdentityBuilder.cs) 的 [IdentityBuilderExtensions](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/IdentityBuilderExtensions.cs) 类的 **AddSignInManager** 扩展方法注册 `CustomSignInManager`.
在你的 `.Web` 项目找到 `YourProjectNameWebModule` 的 `PreConfigureServices` 方法添加以下代码替换老的 `SignInManager`:
````csharp
PreConfigure<IdentityBuilder>(identityBuilder =>
{
identityBuilder.AddSignInManager<CustomSignInManager>();
});
````
## 本文的源代码
你可以在[这里](https://github.com/abpframework/abp-samples/tree/master/aspnet-core/Authentication-Customization)找到已完成的示例源码.
## 另请参阅
* [如何为MVC / Razor页面应用程序自定义登录页面](Customize-Login-Page-MVC.md).
* [身份管理模块](../Modules/Identity.md).

9
docs/zh-Hans/How-To/Index.md

@ -0,0 +1,9 @@
# "如何" 指南
本部分包含一些常见问题的 "如何" 指南. 尽管其中是一些常见的开发任务和ABP并不直接相关,但我们认为有一些具体的示例可以直接与基于ABP的应用程序一起使用.
## Authentication
* [如何为MVC / Razor页面应用程序自定义登录页面](Customize-Login-Page-MVC.md)
* [如何对MVC / Razor页面应用程序使用Azure Active Directory身份验证](Azure-Active-Directory-Authentication-MVC.md)
* [如何为ABP应用程序定制SignIn Manager](Customize-SignIn-Manager.md)

267
docs/zh-Hans/Object-Extensions.md

@ -0,0 +1,267 @@
# 对象扩展
ABP框架提供了 **实体扩展系统** 允许你 **添加额外属性** 到已存在的对象 **无需修改相关类**. 它允许你扩展[应用程序依赖模块](Modules/Index.md)实现的功能,尤其是当你要扩展[模块定义的实体](Customizing-Application-Modules-Extending-Entities.md)和[DTO](Customizing-Application-Modules-Overriding-Services.md)时.
> 你自己的对象通常不需要对象扩展系统,因为你可以轻松的添加常规属性到你的类中.
## IHasExtraProperties 接口
这是一个使类可扩展的接口. 它定义了 `Dictionary` 属性:
````csharp
Dictionary<string, object> ExtraProperties { get; }
````
然后你可以使用此字典添加或获取其他属性.
### 基类
默认以下基类实现了 `IHasExtraProperties` 接口:
* 由 `AggregateRoot` 类实现 (参阅 [entities](Entities.md)).
* 由 `ExtensibleEntityDto`, `ExtensibleAuditedEntityDto`... [DTO](Data-Transfer-Objects.md)基类实现.
* 由 `ExtensibleObject` 实现, 它是一个简单的基类,任何类型的对象都可以继承.
如果你的类从这些类继承,那么你的类也是可扩展的,如果没有,你也可以随时手动继承.
### 基本扩展方法
虽然可以直接使用类的 `ExtraProperties` 属性,但建议使用以下扩展方法使用额外属性.
#### SetProperty
用于设置额外属性值:
````csharp
user.SetProperty("Title", "My Title");
user.SetProperty("IsSuperUser", true);
````
`SetProperty` 返回相同的对象, 你可以使用链式编程:
````csharp
user.SetProperty("Title", "My Title")
.SetProperty("IsSuperUser", true);
````
#### GetProperty
用于读取额外属性的值:
````csharp
var title = user.GetProperty<string>("Title");
if (user.GetProperty<bool>("IsSuperUser"))
{
//...
}
````
* `GetProperty` 是一个泛型方法,对象类型做为泛型参数.
* 如果未设置给定的属性,则返回默认值 (`int` 的默认值为 `0` , `bool` 的默认值是 `false` ... 等).
##### 非基本属性类型
如果您的属性类型不是原始类型(int,bool,枚举,字符串等),你需要使用 `GetProperty` 的非泛型版本,它会返回 `object`.
#### HasProperty
用于检查对象之前是否设置了属性.
#### RemoveProperty
用于从对象中删除属性. 使用此方法代替为属性设置 `null` 值.
### 一些最佳实践
为属性名称使用魔术字符串很危险,因为你很容易输入错误的属性名称-这并不安全;
* 为你的额外属性名称定义一个常量.
* 使用扩展方法轻松设置你的属性.
示例:
````csharp
public static class IdentityUserExtensions
{
private const string TitlePropertyName = "Title";
public static void SetTitle(this IdentityUser user, string title)
{
user.SetProperty(TitlePropertyName, title);
}
public static string GetTitle(this IdentityUser user)
{
return user.GetProperty<string>(TitlePropertyName);
}
}
````
然后, 你可以很容易地设置或获取 `Title` 属性:
````csharp
user.SetTitle("My Title");
var title = user.GetTitle();
````
## Object Extension Manager
你可以为可扩展对象(实现 `IHasExtraProperties`接口)设置任意属性, `ObjectExtensionManager` 用于显式定义可扩展类的其他属性.
显式定义额外的属性有一些用例:
* 允许控制如何在对象到对象的映射上处理额外的属性 (参阅下面的部分).
* 允许定义属性的元数据. 例如你可以在使用[EF Core](Entity-Framework-Core.md)时将额外的属性映射到数据库中的表字段.
> `ObjectExtensionManager` 实现单例模式 (`ObjectExtensionManager.Instance`) ,你应该在应用程序启动之前定义对象扩展. [应用程序启动模板](Startup-Templates/Application.md) 有一些预定义的静态类,可以安全在内部定义对象扩展.
### AddOrUpdate
`AddOrUpdate` 是定义对象额外属性或更新对象额外属性的主要方法.
示例: 为 `IdentityUser` 实体定义额外属性:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdate<IdentityUser>(options =>
{
options.AddOrUpdateProperty<string>("SocialSecurityNumber");
options.AddOrUpdateProperty<bool>("IsSuperUser");
}
);
````
### AddOrUpdateProperty
虽然可以如上所示使用 `AddOrUpdateProperty`, 但如果要定义单个额外的属性,也可以使用快捷的扩展方法:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, string>("SocialSecurityNumber");
````
有时将单个额外属性定义为多种类型是可行的. 你可以使用以下代码,而不是一个一个地定义:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<string>(
new[]
{
typeof(IdentityUserDto),
typeof(IdentityUserCreateDto),
typeof(IdentityUserUpdateDto)
},
"SocialSecurityNumber"
);
````
#### 属性配置
`AddOrUpdateProperty` 还可以为属性定义执行其他配置的操作.
Example:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, string>(
"SocialSecurityNumber",
options =>
{
options.CheckPairDefinitionOnMapping = false;
});
````
> 参阅 "对象到对象映射" 部分了解 `CheckPairDefinitionOnMapping` 选项.
`options` 有一个名为 `Configuration` 的字典,该字典存储对象扩展定义甚至可以扩展. EF Core使用它来将其他属性映射到数据库中的表字段. 请参阅[扩展实体文档](Customizing-Application-Modules-Extending-Entities.md).
## 对象到对象映射
假设你已向可扩展的实体对象添加了额外的属性并使用了自动[对象到对象的映射](Object-To-Object-Mapping.md)将该实体映射到可扩展的DTO类. 在这种情况下你需要格外小心,因为额外属性可能包含**敏感数据**,这些数据对于客户端不可用.
本节提供了一些**好的做法**,可以控制对象映射的额外属性。
### MapExtraPropertiesTo
`MapExtraPropertiesTo` 是ABP框架提供的扩展方法,用于以受控方式将额外的属性从一个对象复制到另一个对象. 示例:
````csharp
identityUser.MapExtraPropertiesTo(identityUserDto);
````
`MapExtraPropertiesTo` 需要在**两侧**(本例中是`IdentityUser` 和 `IdentityUserDto`)**定义属性**. 以将值复制到目标对象. 否则即使源对象(在此示例中为 `identityUser` )中确实存在该值,它也不会复制. 有一些重载此限制的方法.
#### MappingPropertyDefinitionChecks
`MapExtraPropertiesTo` 获取一个附加参数来控制单个映射操作的定义检查:
````csharp
identityUser.MapExtraPropertiesTo(
identityUserDto,
MappingPropertyDefinitionChecks.None
);
````
> 要小心,因为 `MappingPropertyDefinitionChecks.None` 会复制所有的额外属性而不进行任何检查. `MappingPropertyDefinitionChecks` 枚举还有其他成员.
如果要完全禁用属性的定义检查,可以在定义额外的属性(或更新现有定义)时进行,如下所示:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, string>(
"SocialSecurityNumber",
options =>
{
options.CheckPairDefinitionOnMapping = false;
});
````
#### 忽略属性
你可能要在映射操作忽略某些属性:
````csharp
identityUser.MapExtraPropertiesTo(
identityUserDto,
ignoredProperties: new[] {"MySensitiveProp"}
);
````
忽略的属性不会复制到目标对象.
#### AutoMapper集成
如果您使用的是[AutoMapper](https://automapper.org/)库,ABP框架还提供了一种扩展方法来利用上面定义的 `MapExtraPropertiesTo` 方法.
你可以在映射配置文件中使用 `MapExtraProperties()` 方法.
````csharp
public class MyProfile : Profile
{
public MyProfile()
{
CreateMap<IdentityUser, IdentityUserDto>()
.MapExtraProperties();
}
}
````
它与 `MapExtraPropertiesTo()` 方法具有相同的参数。
## Entity Framework Core 数据库映射
如果你使用的是EF Core,可以将额外的属性映射到数据库中的表字段. 例:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, string>(
"SocialSecurityNumber",
options =>
{
options.MapEfCore(b => b.HasMaxLength(32));
}
);
````
参阅 [Entity Framework Core 集成文档](Entity-Framework-Core.md) 了解更多内容.

17
docs/zh-Hans/Object-To-Object-Mapping.md

@ -145,6 +145,23 @@ options.AddProfile<MyProfile>(validate: true);
> 如果你有多个配置文件,并且只需要为其中几个启用验证,那么首先使用`AddMaps`而不进行验证,然后为你想要验证的每个配置文件使用`AddProfile`. > 如果你有多个配置文件,并且只需要为其中几个启用验证,那么首先使用`AddMaps`而不进行验证,然后为你想要验证的每个配置文件使用`AddProfile`.
### 映射对象扩展
[对象扩展系统](Object-Extensions.md) 允许为已存在的类定义额外属性. ABP 框架提供了一个映射定义扩展可以正确的映射两个对象的额外属性.
````csharp
public class MyProfile : Profile
{
public MyProfile()
{
CreateMap<User, UserDto>()
.MapExtraProperties();
}
}
````
如果两个类都是可扩展对象(实现了 `IHasExtraProperties` 接口),建议使用 `MapExtraProperties` 方法. 更多信息请参阅[对象扩展文档](Object-Extensions.md).
## 高级主题 ## 高级主题
### IObjectMapper<TContext> 接口 ### IObjectMapper<TContext> 接口

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 项目

2
docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-I.md

@ -10,7 +10,7 @@
- [Part II: 创建,编辑,删除书籍](Part-II.md) - [Part II: 创建,编辑,删除书籍](Part-II.md)
- [Part III: 集成测试](Part-III.md) - [Part III: 集成测试](Part-III.md)
你可以从[GitHub存储库](https://github.com/abpframework/abp/tree/master/samples/BookStore)访问应用程序的**源代码**. 你可以从[GitHub存储库](https://github.com/abpframework/abp-samples/tree/master/BookStore)访问应用程序的**源代码**.
> 你也可以观看由ABP社区成员为本教程录制的[视频课程](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application). > 你也可以观看由ABP社区成员为本教程录制的[视频课程](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application).

12
docs/zh-Hans/UI/AspNetCore/Bundling-Minification.md

@ -115,7 +115,7 @@ public class MyWebModule : AbpModule
{ {
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)
{ {
Configure<BundlingOptions>(options => Configure<AbpBundlingOptions>(options =>
{ {
options options
.ScriptBundles .ScriptBundles
@ -153,7 +153,7 @@ public class MyWebExtensionModule : AbpModule
{ {
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)
{ {
Configure<BundlingOptions>(options => Configure<AbpBundlingOptions>(options =>
{ {
options options
.ScriptBundles .ScriptBundles
@ -191,7 +191,7 @@ public class MyExtensionGlobalStyleContributor : BundleContributor
然后你可以按照下面的代码使用这个贡献者: 然后你可以按照下面的代码使用这个贡献者:
````C# ````C#
services.Configure<BundlingOptions>(options => services.Configure<AbpBundlingOptions>(options =>
{ {
options options
.ScriptBundles .ScriptBundles
@ -248,10 +248,10 @@ public class MyPrismjsStyleExtension : BundleContributor
} }
```` ````
然后你可以配置 `BundleContributorOptions` 去扩展已存在的 `PrismjsStyleBundleContributor`. 然后你可以配置 `AbpBundleContributorOptions` 去扩展已存在的 `PrismjsStyleBundleContributor`.
````csharp ````csharp
Configure<BundleContributorOptions>(options => Configure<AbpBundleContributorOptions>(options =>
{ {
options options
.Extensions<PrismjsStyleBundleContributor>() .Extensions<PrismjsStyleBundleContributor>()
@ -319,7 +319,7 @@ namespace MyCompany.MyProject
例如: 例如:
````c# ````c#
services.Configure<BundlingOptions>(options => services.Configure<AbpBundlingOptions>(options =>
{ {
options options
.StyleBundles .StyleBundles

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` 文件编写以上代码.

3
docs/zh-Hans/UI/AspNetCore/Libraries/DatatablesNet.md

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

1454
docs/zh-Hans/UI/Common/Utils/Linked-List.md

File diff suppressed because it is too large

26
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 模板",
@ -63,6 +64,10 @@
} }
] ]
}, },
{
"text": "\"如何\" 指南",
"path": "How-To/Index.md"
},
{ {
"text": "从ASP.NET Boilerplate迁移", "text": "从ASP.NET Boilerplate迁移",
"path": "AspNet-Boilerplate-Migration-Guide.md" "path": "AspNet-Boilerplate-Migration-Guide.md"
@ -135,6 +140,14 @@
{ {
"text": "设置管理", "text": "设置管理",
"path": "Settings.md" "path": "Settings.md"
},
{
"text": "数据过滤",
"path": "Data-Filtering.md"
},
{
"text": "对象扩展",
"path": "Object-Extensions.md"
} }
] ]
}, },
@ -282,6 +295,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"
@ -304,6 +321,15 @@
"path": "UI/Angular/Component-Replacement.md" "path": "UI/Angular/Component-Replacement.md"
} }
] ]
},
{
"text": "通用",
"items": [
{
"text": "链表 (双向)",
"path": "UI/Common/Utils/Linked-List.md"
}
]
} }
] ]
}, },

BIN
docs/zh-Hans/images/angular-folder-structure.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

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

Loading…
Cancel
Save