Browse Source

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

pull/3877/head
liangshiwei 6 years ago
parent
commit
7ff494b963
  1. 2
      common.props
  2. 247
      docs/en/Blog-Posts/2020-05-08 v2_7_Release/Post.md
  3. BIN
      docs/en/Blog-Posts/2020-05-08 v2_7_Release/abp-commercial-ui-extensions.png
  4. BIN
      docs/en/Blog-Posts/2020-05-08 v2_7_Release/audit-log-entity-changes.png
  5. BIN
      docs/en/Blog-Posts/2020-05-08 v2_7_Release/easy-crm.png
  6. BIN
      docs/en/Blog-Posts/2020-05-08 v2_7_Release/github-contribution-graph.png
  7. BIN
      docs/en/Blog-Posts/2020-05-08 v2_7_Release/module-entity-extended-ui.png
  8. BIN
      docs/en/Blog-Posts/2020-05-08 v2_7_Release/tenant-entity-changes.png
  9. BIN
      docs/en/Blog-Posts/2020-05-08 v2_7_Release/text-template-content-ui.png
  10. 2
      docs/en/Entity-Framework-Core-Migrations.md
  11. 6
      docs/en/Getting-Started.md
  12. 3
      docs/en/Text-Templating.md
  13. 126
      docs/en/Tutorials/Part-1.md
  14. 159
      docs/en/Tutorials/Part-2.md
  15. BIN
      docs/en/Tutorials/images/bookstore-angular-file-tree.png
  16. BIN
      docs/en/Tutorials/images/bookstore-book-list.png
  17. BIN
      docs/en/Tutorials/images/bookstore-creating-book-list-terminal.png
  18. BIN
      docs/en/Tutorials/images/bookstore-creating-book-module-terminal.png
  19. BIN
      docs/en/Tutorials/images/bookstore-creating-books-module-terminal.png
  20. BIN
      docs/en/Tutorials/images/generated-proxies.png
  21. 5
      docs/zh-Hans/Getting-Started.md
  22. 3
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Pages/Abp/MultiTenancy/AbpTenantController.cs
  23. 12
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/de.json
  24. 12
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/nl.json
  25. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Components/DefaultBrandingProvider.cs
  26. 8
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Components/IBrandingProvider.cs
  27. 3
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/ui-extensions.js
  28. 2
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpApiDefinitionController.cs
  29. 28
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs
  30. 2
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationController.cs
  31. 2
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationScriptController.cs
  32. 2
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpLanguagesController.cs
  33. 2
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/AbpServiceProxyScriptController.cs
  34. 22
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/WebContentFileProvider.cs
  35. 133
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GenerateProxyCommand.cs
  36. 1
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs
  37. 73
      framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/DefaultConventionalRegistrar.cs
  38. 6
      framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Localization/Resources/AbpDdd/de.json
  39. 6
      framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Localization/Resources/AbpDdd/nl.json
  40. 23
      framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/de.json
  41. 23
      framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/nl.json
  42. 7
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Resources/AbpLocalization/de.json
  43. 7
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Resources/AbpLocalization/nl.json
  44. 6
      framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/de.json
  45. 6
      framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/nl.json
  46. 63
      framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/de.json
  47. 63
      framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/nl.json
  48. 2
      framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/cs.json
  49. 34
      framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/de.json
  50. 2
      framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/en.json
  51. 2
      framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/es.json
  52. 34
      framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/nl.json
  53. 2
      framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/pt-BR.json
  54. 2
      framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/ru.json
  55. 2
      framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/tr.json
  56. 2
      framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/zh-Hans.json
  57. 2
      framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/zh-Hant.json
  58. 4
      framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/DictionaryBasedFileProvider.cs
  59. 9
      framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/VirtualFilePathHelper.cs
  60. 7
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/de.json
  61. 7
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/nl.json
  62. 22
      framework/test/Volo.Abp.Core.Tests/Microsoft/Extensions/DependencyInjection/DependencyInjection_Tests.cs
  63. 6
      framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/de.json
  64. 6
      framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/nl.json
  65. 10
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalization_Tests.cs
  66. 7
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/de.json
  67. 7
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/nl.json
  68. 7
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/de.json
  69. 7
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/nl.json
  70. 11
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/de.json
  71. 11
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/nl.json
  72. 8
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/de.json
  73. 6
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/nl.json
  74. 18
      framework/test/Volo.Abp.VirtualFileSystem.Tests/Volo/Abp/VirtualFileSystem/VirtualFileProvider_Tests.cs
  75. 45
      modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/de.json
  76. 45
      modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/nl.json
  77. 2
      modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/AccountController.cs
  78. 14
      modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/de.json
  79. 14
      modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/nl.json
  80. 49
      modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/de.json
  81. 49
      modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/nl.json
  82. 12
      modules/blogging/src/Volo.Blogging.Web/BloggingTwitterOptions.cs
  83. 3
      modules/blogging/src/Volo.Blogging.Web/BloggingUrlOptions.cs
  84. 9
      modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Detail.cshtml
  85. 62
      modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Index.cshtml
  86. 10
      modules/docs/app/VoloDocs.Web/Localization/Resources/VoloDocs/Web/de.json
  87. 10
      modules/docs/app/VoloDocs.Web/Localization/Resources/VoloDocs/Web/nl.json
  88. 7
      modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Documents/IDocumentAdminAppService.cs
  89. 37
      modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/de.json
  90. 37
      modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/nl.json
  91. 10
      modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Projects/IProjectAdminAppService.cs
  92. 9
      modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Projects/ReindexInput.cs
  93. 36
      modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Documents/DocumentAdminAppService.cs
  94. 71
      modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Projects/ProjectAdminAppService.cs
  95. 7
      modules/docs/src/Volo.Docs.Admin.HttpApi/Volo/Docs/Admin/DocumentsAdminController.cs
  96. 14
      modules/docs/src/Volo.Docs.Admin.HttpApi/Volo/Docs/Admin/ProjectsAdminController.cs
  97. 4
      modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/Index.cshtml
  98. 27
      modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/index.js
  99. 44
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/ElasticDocumentFullSearch.cs
  100. 6
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/IDocumentFullSearch.cs

2
common.props

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

247
docs/en/Blog-Posts/2020-05-08 v2_7_Release/Post.md

@ -0,0 +1,247 @@
# ABP Framework v2.7.0 Has Been Released!
The **ABP Framework** & and the **ABP Commercial** v2.7 have been released. We hadn't created blog post for the 2.4, 2.4 and 2.6 releases, so this post will also cover **what's new** with these releases and **what we've done** in the last 2 months.
## About the Release Cycle & Development
Reminding that we had started to release a new minor feature version **in every two weeks**, generally on Thursdays. Our goal is to deliver new features as soon as possible.
We've completed & merged hundreds of issues and pull requests with **1,300+ commits** in the last 7-8 weeks, only for the ABP Framework repository. Daily commit counts are constantly increasing:
![github-contribution-graph](github-contribution-graph.png)
ABP.IO Platform is rapidly growing and we are getting more and more contributions from the community.
## What's New in the ABP Framework?
### Object Extending System
In the last few releases, we've mostly focused on providing ways to extend existing modules when you use them as NuGet/NPM Packages.
The Object Extending System allows module developers to create extensible modules and allows application developers to customize and extend a module easily.
For example, you can add two extension properties to the user entity of the identity module:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdate<IdentityUser>(options =>
{
options.AddOrUpdateProperty<string>("SocialSecurityNumber");
options.AddOrUpdateProperty<bool>("IsSuperUser");
}
);
````
It is easy to define validation rules for the properties:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUserCreateDto, string>(
"SocialSecurityNumber",
options =>
{
options.Attributes.Add(new RequiredAttribute());
options.Attributes.Add(
new StringLengthAttribute(32) {
MinimumLength = 6
}
);
});
````
You can even write custom code to validate the property. It automatically works for the objects those are parameters of an application service, controller or a page.
While extension properties of an entity are normally stored in a single JSON formatted field in the database table, you can easily configure to store a property as a table field using the EF Core mapping:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, string>(
"SocialSecurityNumber",
options =>
{
options.MapEfCore(b => b.HasMaxLength(32));
}
);
````
See the [Object Extensions document](https://docs.abp.io/en/abp/latest/Object-Extensions) for details about this system.
See also the [Customizing the Existing Modules](https://docs.abp.io/en/abp/latest/Customizing-Application-Modules-Guide) guide to learn all the possible customization options.
### Text Templating Package
[Volo.Abp.TextTemplating](https://www.nuget.org/packages/Volo.Abp.TextTemplating) is a new package introduced with the v2.7.0. Previously, [Volo.Abp.Emailing](https://www.nuget.org/packages/Volo.Abp.Emailing) package had a similar functionality but it was limited, experimental and tightly coupled to the emailing.
The new text templating package allows you to define text based templates those can be easily localized and reused. You can define layout templates and share the layout from other templates.
We are currently using it for email sending. A module needs to send an email typically defines a template. Example:
````xml
<h3>{{L "PasswordReset"}}</h3>
<p>{{L "PasswordResetInfoInEmail"}}</p>
<div>
<a href="{{model.link}}">{{L "ResetMyPassword"}}</a>
</div>
````
This is a typical password reset email template.
* The template system is based on the open source [Scriban library](https://github.com/lunet-io/scriban). So it supports if conditions, loops and much more.
* `model` is used to pass data to the template (just like the ASP.NET Core MVC).
* `L` is a special function that localizes the given string.
It is typical to use the same layout for all emails. So, you can define a layout template. This is the standard layout template comes with the framework:
````xml
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
</head>
<body>
{{content}}
</body>
</html>
````
A layout should have a `{{content}}` area to render the child content (just like the `RenderBody()` in the MVC).
It is very easy to override a template content by the final application to customize it.
Whenever you need to render a template, use the `ITemplateRenderer` service by providing the template name and a model. See the [text templating documentation](https://docs.abp.io/en/abp/latest/Text-Templating) for details. We've even created a UI for the ABP Commercial (see the related section below).
### Subscribing to the Exceptions
ABP Framework's [exception handling system](https://docs.abp.io/en/abp/latest/Exception-Handling) automatically handles exceptions and returns an appropriate result to the client. In some cases, you may want to have a callback that is notified whenever an exception occurs. In this way, for example, you can send an email or take any action based on the exception.
Just create a class derived from the `ExceptionSubscriber` class in your application:
````csharp
public class MyExceptionSubscriber : ExceptionSubscriber
{
public override async Task HandleAsync(ExceptionNotificationContext context)
{
//TODO...
}
}
````
See the [exception handling](https://docs.abp.io/en/abp/latest/Exception-Handling) document for more.
### Others
There are many minor features and enhancements made to the framework in the past releases. Here, a few ones:
* Added `AbpLocalizationOptions.DefaultResourceType` to set the default resource type for the application. In this way, the localization system uses the default resource whenever the resource was not specified. The latest application startup template already configures it, but you may want to set it for your existing applications.
* Added `IsEnabled` to permission definition. In this way, you can completely disable a permission and hide the related functionality from the application. This can be a way of feature switch for some applications. See [#3486](https://github.com/abpframework/abp/issues/3486) for usage.
* Added Dutch and German localizations to all the localization resources defined by the framework. Thanks to the contributors.
## What's New in the ABP Commercial
The goal of the [ABP Commercial](https://commercial.abp.io/) is to provide pre-build application functionalities, code generation tools, professional themes, advanced samples and premium support for ABP Framework based projects.
We are working on the ABP Commercial in the parallel to align with the ABP Framework features and provide more modules, theme options and tooling.
This section explains what's going on the ABP Commercial side.
### Module Entity Extension System
Module entity extension system is a higher level API that uses the object extension system (introduced above) and provides an easy way to add extension properties to existing entities. A new extension property easily automatically becomes a part of the HTTP API and the User Interface.
Example: Add a `SocialSecurityNumber` to the user entity of the identity module
````csharp
ObjectExtensionManager.Instance.Modules()
.ConfigureIdentity(identity =>
{
identity.ConfigureUser(user =>
{
user.AddOrUpdateProperty<string>( //property type: string
"SocialSecurityNumber", //property name
property =>
{
//validation rules
property.Attributes.Add(new RequiredAttribute());
property.Attributes.Add(
new StringLengthAttribute(64) {
MinimumLength = 4
}
);
//...other configurations for this property
}
);
});
});
````
With just such a configuration, the user interface will have the new property (on the table and on the create/edit forms):
![module-entity-extended-ui](module-entity-extended-ui.png)
The new property can be easily localized and validated. Currently, it supports primitive types like string, number and boolean, but we planned to add more advanced scenarios by the time (like navigation/lookup properties).
See the [Module Entity Extensions](https://docs.abp.io/en/commercial/latest/guides/module-entity-extensions) guide to learn how to use it and configure details.
#### Other Extension Points
There are also some other pre-defined points to customize and extend the user interface of a depended module:
* You can add a new action for an entity on the data table (left side on the picture below).
* You can add new buttons (or other controls) to the page toolbar (right side on the picture below).
* You can add custom columns to a data table.
![abp-commercial-ui-extensions](abp-commercial-ui-extensions.png)
See the [Customizing the Modules](https://docs.abp.io/en/commercial/latest/guides/customizing-modules) guide to learn all the possible ways to customize a depended module.
### Text Template Management Module
We are introducing a new module with the v2.7 release: [Text Template Management](https://docs.abp.io/en/commercial/latest/modules/text-template-management). It is basically used to edit text/email templates (introduced with the ABP Framework 2.7) on the user interface and save changed in the database.
A screenshot from the content editing for the password reset email template:
![text-template-content-ui](text-template-content-ui.png)
This module comes pre-installed when you create a new project.
### Entity History Views
Audit logging UI module now shows all the entity changes in the application with property change details.
![audit-log-entity-changes](audit-log-entity-changes.png)
You can also check history for an entity when you click to the actions menu for the entity:
![tenant-entity-changes](tenant-entity-changes.png)
### More Samples
We are creating more advanced sample applications built with the ABP Commercial. Easy CRM is one of them which will be available in a few days to the commercial customers.
Here, a screenshot from the Easy CRM dashboard:
![easy-crm](easy-crm.png)
It has accounts, contacts, product groups, products, orders and so on.
### New Modules
We continue to improve existing modules and creating new modules. In addition to the new [text template management](https://docs.abp.io/en/commercial/latest/modules/text-template-management) module introduced above;
* We've recently released a [payment module](https://commercial.abp.io/modules/Volo.Payment) that currently works with PayU and 2Checkout payment gateways. More gateways will be added by the time.
* We've created a simple [Twilio SMS integration](https://docs.abp.io/en/commercial/latest/modules/twilio-sms) module to send SMS over the Twilio.
* We are working on a **chat module** that is currently being developed and will be available in the next weeks.
* We are working on the **organization unit management** system for the identity module to create hierarchical organization units (domain layer will be open source & free).
More modules, theme and tooling options are being developed for the ABP Commercial and the ABP Framework.
## ABP Framework vs ABP Commercial
We ([Volosoft](https://volosoft.com/) - the core team behind the ABP.IO platform), are spending almost equal time on the ABP Framework and the ABP Commercial and we consider the ABP.IO platform as a whole.
[ABP Framework](https://abp.io/) provides all the infrastructure and application independent framework features to make you more productive, focus on your own business code and implement software development best practices. It provides you a well defined and comfortable development experience without repeating yourself.
[ABP Commercial](https://commercial.abp.io/) provides pre-built functionalities, themes and tooling to save your time if your requirements involve these functionalities in addition to the premium support for the framework and the pre-built modules.

BIN
docs/en/Blog-Posts/2020-05-08 v2_7_Release/abp-commercial-ui-extensions.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
docs/en/Blog-Posts/2020-05-08 v2_7_Release/audit-log-entity-changes.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

BIN
docs/en/Blog-Posts/2020-05-08 v2_7_Release/easy-crm.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 KiB

BIN
docs/en/Blog-Posts/2020-05-08 v2_7_Release/github-contribution-graph.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

BIN
docs/en/Blog-Posts/2020-05-08 v2_7_Release/module-entity-extended-ui.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

BIN
docs/en/Blog-Posts/2020-05-08 v2_7_Release/tenant-entity-changes.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
docs/en/Blog-Posts/2020-05-08 v2_7_Release/text-template-content-ui.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

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

@ -546,6 +546,8 @@ Entity extension system solves the main problem of the extra properties: It can
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.
See the [entity extension system](Customizing-Application-Modules-Extending-Entities.md) for details.
###### Creating a New Table
Instead of creating a new entity and mapping to the same table, you can also create **your own table** to store your properties. You typically duplicate some values of the original entity. For example, you can add `Name` field to your own table which is a duplication of the `Name` field in the original table.

6
docs/en/Getting-Started.md

@ -25,6 +25,12 @@ The following tools should be installed on your development machine:
* [Node v12+](https://nodejs.org)
* [Yarn v1.19+](https://classic.yarnpkg.com/)
{{ if Tiered == "Yes" }}
* [Redis](https://redis.io/): The applications use Redis as as [distributed cache](../Caching.md). So, you need to have Redis installed & running.
{{ end }}
> You can use another editor instead of Visual Studio as long as it supports .NET Core and ASP.NET Core.

3
docs/en/Text-Templating.md

@ -0,0 +1,3 @@
# Text-Templating
TODO

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

@ -756,28 +756,28 @@ Open a new command line interface (terminal window) and go to your `angular` fol
yarn
```
#### BooksModule
#### BookModule
Run the following command line to create a new module, named `BooksModule`:
Run the following command line to create a new module, named `BookModule`:
```bash
yarn ng generate module books --route books --module app.module
yarn ng generate module book --routing true
```
![Generating books module](./images/bookstore-creating-books-module-terminal.png)
![Generating books module](./images/bookstore-creating-book-module-terminal.png)
#### Routing
Open the `app-routing.module.ts` file in `src\app` folder. Add the new `import` and replace `books` path as shown below
Open the `app-routing.module.ts` file in `src\app` folder. Add the new `import` and add a route as shown below
```js
import { ApplicationLayoutComponent } from '@abp/ng.theme.basic'; //==> added this line to imports <==
//...replaced original books path with the below
//...added books path with the below to the routes array
{
path: 'books',
component: ApplicationLayoutComponent,
loadChildren: () => import('./books/books.module').then(m => m.BooksModule),
loadChildren: () => import('./book/book.module').then(m => m.BookModule),
data: {
routes: {
name: '::Menu:Books',
@ -789,71 +789,50 @@ import { ApplicationLayoutComponent } from '@abp/ng.theme.basic'; //==> added th
* The `ApplicationLayoutComponent` configuration sets the application layout to the new page. We added the `data` object. The `name` is the menu item name and the `iconClass` is the icon of the menu item.
Run `yarn start` and wait for Angular to serve the application:
```bash
yarn start
```
Open the browser and navigate to http://localhost:4200/books. You'll see a blank page saying "*books works!*".
![initial-books-page](./images/bookstore-initial-books-page-with-layout.png)
#### Book list component
Replace the `books.component.html` in the `app\books` folder with the following content:
```html
<router-outlet></router-outlet>
```
Then run the command below on the terminal in the root folder to generate a new component, named book-list:
Run the command below on the terminal in the root folder to generate a new component, named book-list:
```bash
yarn ng generate component books/book-list
yarn ng generate component book/book-list
```
![Creating books list](./images/bookstore-creating-book-list-terminal.png)
Open `books.module.ts` file in the `app\books` folder and replace the content as below:
Open `book.module.ts` file in the `app\book` folder and replace the content as below:
```js
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { BooksRoutingModule } from './books-routing.module';
import { BooksComponent } from './books.component';
import { BookRoutingModule } from './book-routing.module';
import { BookListComponent } from './book-list/book-list.component';
import { SharedModule } from '../shared/shared.module'; //<== added this line ==>
@NgModule({
declarations: [BooksComponent, BookListComponent],
declarations: [BookListComponent],
imports: [
CommonModule,
BooksRoutingModule,
BookRoutingModule,
SharedModule, //<== added this line ==>
]
],
})
export class BooksModule { }
export class BookModule {}
```
* We imported `SharedModule` and added to `imports` array.
Open `books-routing.module.ts` file in the `app\books` folder and replace the content as below:
Open `book-routing.module.ts` file in the `app\book` folder and replace the content as below:
```js
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { BookListComponent } from './book-list/book-list.component'; // <== added this line ==>
import { BooksComponent } from './books.component';
import { BookListComponent } from './book-list/book-list.component'; //<== added this line ==>
//<== replaced routes ==>
// <== replaced routes ==>
const routes: Routes = [
{
path: '',
component: BooksComponent,
children: [{ path: '', component: BookListComponent }],
component: BookListComponent,
},
];
@ -861,36 +840,42 @@ const routes: Routes = [
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class BooksRoutingModule { }
export class BookRoutingModule { }
```
* We imported `BookListComponent` and replaced `routes` const.
We'll see **book-list works!** text on the books page:
Run `yarn start` and wait for Angular to serve the application:
```bash
yarn start
```
Open the browser and navigate to http://localhost:4200/books. We'll see **book-list works!** text on the books page:
![Initial book list page](./images/bookstore-initial-book-list-page.png)
#### Create BooksState
#### Create BookState
Run the following command in the terminal to create a new state, named `BooksState`:
```bash
npx @ngxs/cli --name books --directory src/app/books
npx @ngxs/cli --name book --directory src/app/book
```
* This command creates `books.state.ts` and `books.actions.ts` files in the `src/app/books/state` folder. See the [NGXS CLI documentation](https://www.ngxs.io/plugins/cli).
* This command creates `book.state.ts` and `book.actions.ts` files in the `src/app/book/state` folder. See the [NGXS CLI documentation](https://www.ngxs.io/plugins/cli).
Import the `BooksState` to the `app.module.ts` in the `src/app` folder and then add the `BooksState` to `forRoot` static method of `NgxsModule` as an array element of the first parameter of the method.
Import the `BookState` to the `app.module.ts` in the `src/app` folder and then add the `BookState` to `forRoot` static method of `NgxsModule` as an array element of the first parameter of the method.
```js
// ...
import { BooksState } from './books/state/books.state'; //<== imported BooksState ==>
import { BookState } from './books/state/book.state'; //<== imported BookState ==>
@NgModule({
imports: [
// other imports
NgxsModule.forRoot([BooksState]), //<== added BooksState ==>
NgxsModule.forRoot([BookState]), //<== added BookState ==>
//other imports
],
@ -919,46 +904,46 @@ The generated files looks like below:
Actions can either be thought of as a command which should trigger something to happen, or as the resulting event of something that has already happened. [See NGXS Actions documentation](https://www.ngxs.io/concepts/actions).
Open the `books.actions.ts` file in `app/books/state` folder and replace the content below:
Open the `book.actions.ts` file in `app/book/state` folder and replace the content below:
```js
export class GetBooks {
static readonly type = '[Books] Get';
static readonly type = '[Book] Get';
}
```
#### Implement BooksState
#### Implement BookState
Open the `books.state.ts` file in `app/books/state` folder and replace the content below:
Open the `book.state.ts` file in `app/book/state` folder and replace the content below:
```js
import { PagedResultDto } from '@abp/ng.core';
import { State, Action, StateContext, Selector } from '@ngxs/store';
import { GetBooks } from './books.actions';
import { BookService } from '../../app/shared/services';
import { GetBooks } from './book.actions';
import { BookService } from '../services';
import { tap } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { BookDto } from '../../app/shared/models';
import { BookDto } from '../models';
export class BooksStateModel {
export class BookStateModel {
public book: PagedResultDto<BookDto>;
}
@State<BooksStateModel>({
name: 'BooksState',
defaults: { book: {} } as BooksStateModel,
@State<BookStateModel>({
name: 'BookState',
defaults: { book: {} } as BookStateModel,
})
@Injectable()
export class BooksState {
export class BookState {
@Selector()
static getBooks(state: BooksStateModel) {
static getBooks(state: BookStateModel) {
return state.book.items || [];
}
constructor(private bookService: BookService) {}
@Action(GetBooks)
get(ctx: StateContext<BooksStateModel>) {
get(ctx: StateContext<BookStateModel>) {
return this.bookService.getListByInput().pipe(
tap((booksResponse) => {
ctx.patchState({
@ -969,22 +954,23 @@ export class BooksState {
}
}
```
* We added the book property to BooksStateModel model.
* We added the `GetBooks` action that retrieves the books data via `BooksService` that generated via ABP CLI and patches the state.
* We added the book property to BookStateModel model.
* We added the `GetBooks` action that retrieves the book data via `BookService` that generated via ABP CLI and patches the state.
* `NGXS` requires to return the observable without subscribing it in the get function.
#### BookListComponent
Open the `book-list.component.ts` file in `app\books\book-list` folder and replace the content as below:
Open the `book-list.component.ts` file in `app\book\book-list` folder and replace the content as below:
```js
import { Component, OnInit } from '@angular/core';
import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { BookDto, BookType } from '../../app/shared/models';
import { GetBooks } from '../state/books.actions';
import { BooksState } from '../state/books.state';
import { BookDto, BookType } from '../models';
import { GetBooks } from '../state/book.actions';
import { BookState } from '../state/book.state';
@Component({
selector: 'app-book-list',
@ -992,7 +978,7 @@ import { BooksState } from '../state/books.state';
styleUrls: ['./book-list.component.scss'],
})
export class BookListComponent implements OnInit {
@Select(BooksState.getBooks)
@Select(BookState.getBooks)
books$: Observable<BookDto[]>;
booksType = BookType;
@ -1018,7 +1004,7 @@ export class BookListComponent implements OnInit {
* We added the `get` function that updates store to get the books.
* See the [Dispatching actions](https://ngxs.gitbook.io/ngxs/concepts/store#dispatching-actions) and [Select](https://ngxs.gitbook.io/ngxs/concepts/select) on the `NGXS` documentation for more information on these `NGXS` features.
Open the `book-list.component.html` file in `app\books\book-list` folder and replace the content as below:
Open the `book-list.component.html` file in `app\book\book-list` folder and replace the content as below:
```html
<div class="card">

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

@ -458,54 +458,54 @@ In this section, you will learn how to create a new modal dialog form to create
#### State definitions
Open `books.action.ts` in `books\state` folder and replace the content as below:
Open `book.action.ts` in `app\book\state` folder and replace the content as below:
```js
import { CreateUpdateBookDto } from '../../app/shared/models'; //<== added this line ==>
import { CreateUpdateBookDto } from '../models'; //<== added this line ==>
export class GetBooks {
static readonly type = '[Books] Get';
static readonly type = '[Book] Get';
}
// added CreateUpdateBook class
export class CreateUpdateBook {
static readonly type = '[Books] Create Update Book';
static readonly type = '[Book] Create Update Book';
constructor(public payload: CreateUpdateBookDto) { }
}
```
* We imported the `CreateUpdateBookDto` model and created the `CreateUpdateBook` action.
Open `books.state.ts` file in `books\state` folder and replace the content as below:
Open `book.state.ts` file in `app\book\state` folder and replace the content as below:
```js
import { PagedResultDto } from '@abp/ng.core';
import { State, Action, StateContext, Selector } from '@ngxs/store';
import { GetBooks, CreateUpdateBook } from './books.actions'; // <== added CreateUpdateBook==>
import { BookService } from '../../app/shared/services';
import { GetBooks, CreateUpdateBook } from './book.actions'; // <== added CreateUpdateBook==>
import { BookService } from '../services';
import { tap } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { BookDto } from '../../app/shared/models';
import { BookDto } from '../models';
export class BooksStateModel {
export class BookStateModel {
public book: PagedResultDto<BookDto>;
}
@State<BooksStateModel>({
name: 'BooksState',
defaults: { book: {} } as BooksStateModel,
@State<BookStateModel>({
name: 'BookState',
defaults: { book: {} } as BookStateModel,
})
@Injectable()
export class BooksState {
export class BookState {
@Selector()
static getBooks(state: BooksStateModel) {
static getBooks(state: BookStateModel) {
return state.book.items || [];
}
constructor(private bookService: BookService) {}
@Action(GetBooks)
get(ctx: StateContext<BooksStateModel>) {
get(ctx: StateContext<BookStateModel>) {
return this.bookService.getListByInput().pipe(
tap((bookResponse) => {
ctx.patchState({
@ -517,7 +517,7 @@ export class BooksState {
// added CreateUpdateBook action listener
@Action(CreateUpdateBook)
save(ctx: StateContext<BooksStateModel>, action: CreateUpdateBook) {
save(ctx: StateContext<BookStateModel>, action: CreateUpdateBook) {
return this.bookService.createByInput(action.payload);
}
}
@ -605,16 +605,16 @@ 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.
* We added `New book` button to the `AbpContentToolbar`.
Open `book-list.component.ts` file in `books\book-list` folder and replace the content as below:
Open `book-list.component.ts` file in `app\book\book-list` folder and replace the content as below:
```js
import { Component, OnInit } from '@angular/core';
import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { BookDto, BookType } from '../../app/shared/models';
import { GetBooks } from '../state/books.actions';
import { BooksState } from '../state/books.state';
import { BookDto, BookType } from '../models';
import { GetBooks } from '../state/book.actions';
import { BookState } from '../state/book.state';
@Component({
selector: 'app-book-list',
@ -622,7 +622,7 @@ import { BooksState } from '../state/books.state';
styleUrls: ['./book-list.component.scss'],
})
export class BookListComponent implements OnInit {
@Select(BooksState.getBooks)
@Select(BookState.getBooks)
books$: Observable<BookDto[]>;
booksType = BookType;
@ -662,16 +662,16 @@ You can open your browser and click **New book** button to see the new modal.
[Reactive forms](https://angular.io/guide/reactive-forms) provide a model-driven approach to handling form inputs whose values change over time.
Open `book-list.component.ts` file in `app\books\book-list` folder and replace the content as below:
Open `book-list.component.ts` file in `app\book\book-list` folder and replace the content as below:
```js
import { Component, OnInit } from '@angular/core';
import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { BookDto, BookType } from '../../app/shared/models';
import { GetBooks } from '../state/books.actions';
import { BooksState } from '../state/books.state';
import { BookDto, BookType } from '../models';
import { GetBooks } from '../state/book.actions';
import { BookState } from '../state/book.state';
import { FormGroup, FormBuilder, Validators } from '@angular/forms'; // <== added this line ==>
@Component({
@ -680,7 +680,7 @@ import { FormGroup, FormBuilder, Validators } from '@angular/forms'; // <== adde
styleUrls: ['./book-list.component.scss'],
})
export class BookListComponent implements OnInit {
@Select(BooksState.getBooks)
@Select(BookState.getBooks)
books$: Observable<BookDto[]>;
booksType = BookType;
@ -774,42 +774,40 @@ Open `book-list.component.html` in `app\books\book-list` folder and replace `<ng
#### Datepicker requirements
Open `books.module.ts` file in `app\books` folder and replace the content as below:
Open `book.module.ts` file in `app\book` folder and replace the content as below:
```js
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { BooksRoutingModule } from './books-routing.module';
import { BooksComponent } from './books.component';
import { BookRoutingModule } from './book-routing.module';
import { BookListComponent } from './book-list/book-list.component';
import { SharedModule } from '../shared/shared.module';
import { NgbDatepickerModule } from '@ng-bootstrap/ng-bootstrap'; //<== added this line ==>
@NgModule({
declarations: [BooksComponent, BookListComponent],
declarations: [BookListComponent],
imports: [
CommonModule,
BooksRoutingModule,
BookRoutingModule,
SharedModule,
NgbDatepickerModule //<== added this line ==>
]
NgbDatepickerModule, //<== added this line ==>
],
})
export class BooksModule { }
export class BookModule {}
```
* We imported `NgbDatepickerModule` to be able to use the date picker.
Open `book-list.component.ts` file in `app\books\book-list` folder and replace the content as below:
Open `book-list.component.ts` file in `app\book\book-list` folder and replace the content as below:
```js
import { Component, OnInit } from '@angular/core';
import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { BookDto, BookType } from '../../app/shared/models';
import { GetBooks } from '../state/books.actions';
import { BooksState } from '../state/books.state';
import { BookDto, BookType } from '../models';
import { GetBooks } from '../state/book.actions';
import { BookState } from '../state/book.state';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; // <== added this line ==>
@ -820,7 +818,7 @@ import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap
providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], // <== added this line ==>
})
export class BookListComponent implements OnInit {
@Select(BooksState.getBooks)
@Select(BookState.getBooks)
books$: Observable<BookDto[]>;
booksType = BookType;
@ -885,16 +883,16 @@ Now, you can open your browser to see the changes:
#### Saving the book
Open `book-list.component.ts` file in `app\books\book-list` folder and replace the content as below:
Open `book-list.component.ts` file in `app\book\book-list` folder and replace the content as below:
```js
import { Component, OnInit } from '@angular/core';
import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { BookDto, BookType } from '../../app/shared/models';
import { GetBooks, CreateUpdateBook } from '../state/books.actions'; // <== added CreateUpdateBook ==>
import { BooksState } from '../state/books.state';
import { BookDto, BookType } from '../models';
import { GetBooks, CreateUpdateBook } from '../state/book.actions'; // <== added CreateUpdateBook ==>
import { BookState } from '../state/book.state';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap';
@ -905,12 +903,11 @@ import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap
providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }],
})
export class BookListComponent implements OnInit {
@Select(BooksState.getBooks)
@Select(BookState.getBooks)
books$: Observable<BookDto[]>;
booksType = BookType;
//added bookTypeArr array
bookTypeArr = Object.keys(BookType).filter(
(bookType) => typeof this.booksType[bookType] === 'number'
);
@ -949,7 +946,7 @@ export class BookListComponent implements OnInit {
});
}
//<== added save ==>
// <== added save ==>
save() {
if (this.form.invalid) {
return;
@ -967,7 +964,7 @@ export class BookListComponent implements OnInit {
* We imported `CreateUpdateBook`.
* We added `save` method
Open `book-list.component.html` in `app\books\book-list` folder and add the following `abp-button` to save the new book.
Open `book-list.component.html` in `app\book\book-list` folder and add the following `abp-button` to save the new book.
```html
<ng-template #abpFooter>
@ -1001,28 +998,28 @@ The final modal UI looks like below:
#### CreateUpdateBook action
Open the `books.actions.ts` in `books\state` folder and replace the content as below:
Open the `book.actions.ts` in `app\book\state` folder and replace the content as below:
```js
import { CreateUpdateBookDto } from '../../app/shared/models';
import { CreateUpdateBookDto } from '../models';
export class GetBooks {
static readonly type = '[Books] Get';
static readonly type = '[Book] Get';
}
export class CreateUpdateBook {
static readonly type = '[Books] Create Update Book';
constructor(public payload: CreateUpdateBookDto, public id?: string) { } // <== added id parameter ==>
static readonly type = '[Book] Create Update Book';
constructor(public payload: CreateUpdateBookDto, public id?: string) {} // <== added id parameter ==>
}
```
* We added `id` parameter to the `CreateUpdateBook` action's constructor.
Open the `books.state.ts` in `books\state` folder and replace the `save` method as below:
Open the `book.state.ts` in `app\book\state` folder and replace the `save` method as below:
```js
@Action(CreateUpdateBook)
save(ctx: StateContext<BooksStateModel>, action: CreateUpdateBook) {
save(ctx: StateContext<BookStateModel>, action: CreateUpdateBook) {
if (action.id) {
return this.bookService.updateByIdAndInput(action.payload, action.id);
} else {
@ -1033,19 +1030,19 @@ save(ctx: StateContext<BooksStateModel>, action: CreateUpdateBook) {
#### BookListComponent
Open `book-list.component.ts` in `app\books\book-list` folder and inject `BookService` dependency by adding it to the constructor and add a variable named `selectedBook`.
Open `book-list.component.ts` in `app\book\book-list` folder and inject `BookService` dependency by adding it to the constructor and add a variable named `selectedBook`.
```js
import { Component, OnInit } from '@angular/core';
import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { BookDto, BookType } from '../../app/shared/models';
import { GetBooks, CreateUpdateBook } from '../state/books.actions';
import { BooksState } from '../state/books.state';
import { BookDto, BookType } from '../models';
import { GetBooks, CreateUpdateBook } from '../state/book.actions';
import { BookState } from '../state/book.state';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap';
import { BookService } from '../../app/shared/services'; // <== imported BookService ==>
import { BookService } from '../services'; // <== imported BookService ==>
@Component({
selector: 'app-book-list',
@ -1054,7 +1051,7 @@ import { BookService } from '../../app/shared/services'; // <== imported BookSer
providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }],
})
export class BookListComponent implements OnInit {
@Select(BooksState.getBooks)
@Select(BookState.getBooks)
books$: Observable<BookDto[]>;
booksType = BookType;
@ -1141,7 +1138,7 @@ export class BookListComponent implements OnInit {
#### Add "Actions" dropdown to the table
Open the `book-list.component.html` in `app\books\book-list` folder and replace the `<div class="card-body">` tag as below:
Open the `book-list.component.html` in `app\book\book-list` folder and replace the `<div class="card-body">` tag as below:
```html
<div class="card-body">
@ -1199,7 +1196,7 @@ The final UI looks like as below:
![Action buttons](./images/bookstore-actions-buttons.png)
Open `book-list.component.html` in `app\books\book-list` folder and find the `<ng-template #abpHeader>` tag and replace the content as below.
Open `book-list.component.html` in `app\book\book-list` folder and find the `<ng-template #abpHeader>` tag and replace the content as below.
```html
<ng-template #abpHeader>
@ -1213,45 +1210,45 @@ Open `book-list.component.html` in `app\books\book-list` folder and find the `<n
#### DeleteBook action
Open `books.actions.ts` in `books\state `folder and add an action named `DeleteBook`.
Open `book.actions.ts` in `app\book\state` folder and add an action named `DeleteBook`.
```js
export class DeleteBook {
static readonly type = '[Books] Delete';
static readonly type = '[Book] Delete';
constructor(public id: string) {}
}
```
Open the `books.state.ts` in `books\state` folder and replace the content as below:
Open the `book.state.ts` in `app\book\state` folder and replace the content as below:
```js
import { PagedResultDto } from '@abp/ng.core';
import { State, Action, StateContext, Selector } from '@ngxs/store';
import { GetBooks, CreateUpdateBook, DeleteBook } from './books.actions'; // <== added DeleteBook==>
import { BookService } from '../../app/shared/services';
import { GetBooks, CreateUpdateBook, DeleteBook } from './book.actions'; // <== added DeleteBook==>
import { BookService } from '../services';
import { tap } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { BookDto } from '../../app/shared/models';
import { BookDto } from '../models';
export class BooksStateModel {
export class BookStateModel {
public book: PagedResultDto<BookDto>;
}
@State<BooksStateModel>({
name: 'BooksState',
defaults: { book: {} } as BooksStateModel,
@State<BookStateModel>({
name: 'BookState',
defaults: { book: {} } as BookStateModel,
})
@Injectable()
export class BooksState {
export class BookState {
@Selector()
static getBooks(state: BooksStateModel) {
static getBooks(state: BookStateModel) {
return state.book.items || [];
}
constructor(private bookService: BookService) {}
@Action(GetBooks)
get(ctx: StateContext<BooksStateModel>) {
get(ctx: StateContext<BookStateModel>) {
return this.bookService.getListByInput().pipe(
tap((booksResponse) => {
ctx.patchState({
@ -1262,7 +1259,7 @@ export class BooksState {
}
@Action(CreateUpdateBook)
save(ctx: StateContext<BooksStateModel>, action: CreateUpdateBook) {
save(ctx: StateContext<BookStateModel>, action: CreateUpdateBook) {
if (action.id) {
return this.bookService.updateByIdAndInput(action.payload, action.id);
} else {
@ -1272,7 +1269,7 @@ export class BooksState {
// <== added DeleteBook action listener ==>
@Action(DeleteBook)
delete(ctx: StateContext<BooksStateModel>, action: DeleteBook) {
delete(ctx: StateContext<BookStateModel>, action: DeleteBook) {
return this.bookService.deleteById(action.id);
}
}
@ -1285,7 +1282,7 @@ export class BooksState {
#### Delete confirmation popup
Open `book-list.component.ts` in`app\books\book-list` folder and inject the `ConfirmationService`.
Open `book-list.component.ts` in`app\book\book-list` folder and inject the `ConfirmationService`.
Replace the constructor as below:
@ -1309,7 +1306,7 @@ See the [Confirmation Popup documentation](https://docs.abp.io/en/abp/latest/UI/
In the `book-list.component.ts` add a delete method :
```js
import { GetBooks, CreateUpdateBook, DeleteBook } from '../state/books.actions' ;// <== imported DeleteBook ==>
import { GetBooks, CreateUpdateBook, DeleteBook } from '../state/book.actions' ;// <== imported DeleteBook ==>
import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared'; //<== imported Confirmation ==>
@ -1335,7 +1332,7 @@ The `delete` method shows a confirmation popup and subscribes for the user respo
#### Add a delete button
Open `book-list.component.html` in `app\books\book-list` folder and modify the `ngbDropdownMenu` to add the delete button as shown below:
Open `book-list.component.html` in `app\book\book-list` folder and modify the `ngbDropdownMenu` to add the delete button as shown below:
```html
<div ngbDropdownMenu>

BIN
docs/en/Tutorials/images/bookstore-angular-file-tree.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 57 KiB

BIN
docs/en/Tutorials/images/bookstore-book-list.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 49 KiB

BIN
docs/en/Tutorials/images/bookstore-creating-book-list-terminal.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 158 KiB

BIN
docs/en/Tutorials/images/bookstore-creating-book-module-terminal.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

BIN
docs/en/Tutorials/images/bookstore-creating-books-module-terminal.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

BIN
docs/en/Tutorials/images/generated-proxies.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 23 KiB

5
docs/zh-Hans/Getting-Started.md

@ -24,6 +24,11 @@
* [Node v12+](https://nodejs.org)
* [Yarn v1.19+](https://classic.yarnpkg.com/)
{{ if Tiered == "Yes" }}
* [Redis](https://redis.io/): 应用程序将Redis用作[分布式缓存](../Caching.md). 因此你需要安装并运行Redis.
{{ end }}
> 你可以也使用其他支持.NET Core 和 ASP.NET Core的编辑器.

3
framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Pages/Abp/MultiTenancy/AbpTenantController.cs

@ -1,11 +1,14 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.MultiTenancy;
namespace Pages.Abp.MultiTenancy
{
[Area("abp")]
[RemoteService(Name = "abp")]
[Route("api/abp/multi-tenancy")]
public class AbpTenantController : AbpController, IAbpTenantAppService
{

12
framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/de.json

@ -0,0 +1,12 @@
{
"culture": "de",
"texts": {
"GivenTenantIsNotAvailable": "Der angegebene Mandant ist nicht verfügbar: {0}",
"Tenant": "Mandant",
"Switch": "wechseln",
"Name": "Name",
"SwitchTenantHint": "Lassen Sie das Namensfeld leer, um auf die Host-Seite zu wechseln.",
"SwitchTenant": "Mandant wechseln",
"NotSelected": "Nicht ausgewählt"
}
}

12
framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/nl.json

@ -0,0 +1,12 @@
{
"culture": "nl",
"texts": {
"GivenTenantIsNotAvailable": "Gegeven klant is niet beschikbaar: {0}",
"Tenant": "Klant",
"Switch": "Schakel over",
"Name": "Name",
"SwitchTenantHint": "Laat het naamveld leeg om over te schakelen naar de hostkant.",
"SwitchTenant": "Klant wisselen",
"NotSelected": "Niet geselecteerd"
}
}

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Components/DefaultBrandingProvider.cs

@ -7,5 +7,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Components
public virtual string AppName => "MyApplication";
public virtual string LogoUrl => null;
public virtual string LogoReverseUrl => null;
}
}

8
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Components/IBrandingProvider.cs

@ -4,6 +4,14 @@
{
string AppName { get; }
/// <summary>
/// Logo on white background
/// </summary>
string LogoUrl { get; }
/// <summary>
/// Logo on dark background
/// </summary>
string LogoReverseUrl { get; }
}
}

3
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/ui-extensions.js

@ -144,7 +144,8 @@
var tableProperty = properties[i];
columnConfigs.push({
title: localizeDisplayName(tableProperty.name, tableProperty.config.displayName),
data: "extraProperties." + tableProperty.name
data: "extraProperties." + tableProperty.name,
orderable: false
});
}

2
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpApiDefinitionController.cs

@ -3,6 +3,8 @@ using Volo.Abp.Http.Modeling;
namespace Volo.Abp.AspNetCore.Mvc.ApiExploring
{
[Area("abp")]
[RemoteService(Name = "abp")]
[Route("api/abp/api-definition")]
public class AbpApiDefinitionController : AbpController, IRemoteService
{

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

@ -63,7 +63,9 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
{
//TODO: Optimize & cache..?
return new ApplicationConfigurationDto
Logger.LogDebug("Executing AbpApplicationConfigurationAppService.GetAsync()...");
var result = new ApplicationConfigurationDto
{
Auth = await GetAuthConfigAsync(),
Features = await GetFeaturesConfigAsync(),
@ -74,6 +76,10 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
CurrentTenant = GetCurrentTenant(),
ObjectExtensions = _cachedObjectExtensionsDtoService.Get()
};
Logger.LogDebug("Executed AbpApplicationConfigurationAppService.GetAsync().");
return result;
}
protected virtual CurrentTenantDto GetCurrentTenant()
@ -107,35 +113,25 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
protected virtual async Task<ApplicationAuthConfigurationDto> GetAuthConfigAsync()
{
Logger.LogDebug("Executing AbpApplicationConfigurationAppService.GetAuthConfigAsync()");
var authConfig = new ApplicationAuthConfigurationDto();
var policyNames = await _abpAuthorizationPolicyProvider.GetPoliciesNamesAsync();
Logger.LogDebug($"GetPoliciesNamesAsync returns {policyNames.Count} items.");
foreach (var policyName in policyNames)
{
authConfig.Policies[policyName] = true;
Logger.LogDebug($"_authorizationService.IsGrantedAsync? {policyName}");
if (await _authorizationService.IsGrantedAsync(policyName))
{
authConfig.GrantedPolicies[policyName] = true;
}
}
Logger.LogDebug("Executed AbpApplicationConfigurationAppService.GetAuthConfigAsync()");
return authConfig;
}
protected virtual async Task<ApplicationLocalizationConfigurationDto> GetLocalizationConfigAsync()
{
Logger.LogDebug("Executing AbpApplicationConfigurationAppService.GetLocalizationConfigAsync()");
var localizationConfig = new ApplicationLocalizationConfigurationDto();
localizationConfig.Languages.AddRange(await _languageProvider.GetLanguagesAsync());
@ -165,8 +161,6 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
);
}
Logger.LogDebug("Executed AbpApplicationConfigurationAppService.GetLocalizationConfigAsync()");
return localizationConfig;
}
@ -197,8 +191,6 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
private async Task<ApplicationSettingConfigurationDto> GetSettingConfigAsync()
{
Logger.LogDebug("Executing AbpApplicationConfigurationAppService.GetSettingConfigAsync()");
var result = new ApplicationSettingConfigurationDto
{
Values = new Dictionary<string, string>()
@ -214,15 +206,11 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
result.Values[settingDefinition.Name] = await _settingProvider.GetOrNullAsync(settingDefinition.Name);
}
Logger.LogDebug("Executed AbpApplicationConfigurationAppService.GetSettingConfigAsync()");
return result;
}
protected virtual async Task<ApplicationFeatureConfigurationDto> GetFeaturesConfigAsync()
{
Logger.LogDebug("Executing AbpApplicationConfigurationAppService.GetFeaturesConfigAsync()");
var result = new ApplicationFeatureConfigurationDto();
foreach (var featureDefinition in _featureDefinitionManager.GetAll())
@ -235,8 +223,6 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
result.Values[featureDefinition.Name] = await FeatureChecker.GetOrNullAsync(featureDefinition.Name);
}
Logger.LogDebug("Executed AbpApplicationConfigurationAppService.GetFeaturesConfigAsync()");
return result;
}
}

2
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationController.cs

@ -3,6 +3,8 @@ using Microsoft.AspNetCore.Mvc;
namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
{
[Area("abp")]
[RemoteService(Name = "abp")]
[Route("api/abp/application-configuration")]
public class AbpApplicationConfigurationController : AbpController, IAbpApplicationConfigurationAppService
{

2
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationScriptController.cs

@ -12,6 +12,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
[Area("Abp")]
[Route("Abp/ApplicationConfigurationScript")]
[DisableAuditing]
[RemoteService(false)]
[ApiExplorerSettings(IgnoreApi = true)]
public class AbpApplicationConfigurationScriptController : AbpController
{
private readonly IAbpApplicationConfigurationAppService _configurationAppService;

2
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpLanguagesController.cs

@ -8,6 +8,8 @@ namespace Volo.Abp.AspNetCore.Mvc.Localization
{
[Area("Abp")]
[Route("Abp/Languages/[action]")]
[RemoteService(false)]
[ApiExplorerSettings(IgnoreApi = true)]
public class AbpLanguagesController : AbpController
{
[HttpGet]

2
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/AbpServiceProxyScriptController.cs

@ -10,6 +10,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ProxyScripting
[Area("Abp")]
[Route("Abp/ServiceProxyScript")]
[DisableAuditing]
[RemoteService(false)]
[ApiExplorerSettings(IgnoreApi = true)]
public class AbpServiceProxyScriptController : AbpController
{
private readonly IProxyScriptManager _proxyScriptManager;

22
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/WebContentFileProvider.cs

@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using Volo.Abp.DependencyInjection;
@ -91,9 +93,27 @@ namespace Volo.Abp.AspNetCore.VirtualFileSystem
protected virtual IFileProvider CreateFileProvider()
{
return new CompositeFileProvider(
var fileProviders = new List<IFileProvider>()
{
new PhysicalFileProvider(_hostingEnvironment.ContentRootPath),
_virtualFileProvider
};
if (_hostingEnvironment.IsDevelopment() &&
_hostingEnvironment.WebRootFileProvider is CompositeFileProvider compositeFileProvider)
{
var staticWebAssetsFileProvider = compositeFileProvider
.FileProviders
.FirstOrDefault(f => f.GetType().Name.Equals("StaticWebAssetsFileProvider"));
if (staticWebAssetsFileProvider != null)
{
fileProviders.Add(staticWebAssetsFileProvider);
}
}
return new CompositeFileProvider(
fileProviders
);
}

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

@ -20,6 +20,7 @@ namespace Volo.Abp.Cli.Commands
{
public static Dictionary<string, Dictionary<string, string>> propertyList = new Dictionary<string, Dictionary<string, string>>();
public ILogger<GenerateProxyCommand> Logger { get; set; }
public static string outputPrefix = "src/app";
protected TemplateProjectBuilder TemplateProjectBuilder { get; }
@ -100,14 +101,22 @@ namespace Volo.Abp.Cli.Commands
Logger.LogInformation($"{rootPath} directory is creating");
Directory.CreateDirectory($"src/app/{rootPath}/shared/models");
Directory.CreateDirectory($"src/app/{rootPath}/shared/services");
if (rootPath == "app")
{
outputPrefix = "src";
}
else
{
outputPrefix = "src/app";
}
var serviceIndexList = new List<string>();
var modelIndexList = new List<string>();
Directory.CreateDirectory($"{outputPrefix}/{rootPath}");
foreach (var controller in moduleValue.Root.ToList().Select(item => item.First))
{
var serviceIndexList = new List<string>();
var modelIndexList = new List<string>();
var serviceFileText = new StringBuilder();
serviceFileText.AppendLine("[firstTypeList]");
@ -128,6 +137,12 @@ namespace Volo.Abp.Cli.Commands
var controllerName = (string)controller["controllerName"];
var controllerServiceName = controllerName.PascalToKebabCase() + ".service.ts";
var controllerPathName = controllerName.ToLower().Replace("controller", "");
controllerPathName = (controllerPathName.StartsWith(rootPath)) ? controllerPathName.Substring(rootPath.Length) : controllerPathName;
Directory.CreateDirectory($"{outputPrefix}/{rootPath}/{controllerPathName}/models");
Directory.CreateDirectory($"{outputPrefix}/{rootPath}/{controllerPathName}/services");
foreach (var actionItem in controller["actions"])
{
var action = actionItem.First;
@ -157,7 +172,7 @@ namespace Volo.Abp.Cli.Commands
var isOptional = (bool)parameter["isOptional"];
var defaultValue = (string)parameter["defaultValue"];
var modelIndex = CreateType(data, (string)parameter["type"], rootPath, modelIndexList);
var modelIndex = CreateType(data, (string)parameter["type"], rootPath, modelIndexList, controllerPathName);
if (!string.IsNullOrWhiteSpace(modelIndex))
{
@ -194,7 +209,7 @@ namespace Volo.Abp.Cli.Commands
parameterModel = AddParameter(name, typeSimple, isOptional, defaultValue, bindingSourceId, parameterModel);
}
modelIndex = CreateType(data, (string)parameterOnMethod["type"], rootPath, modelIndexList);
modelIndex = CreateType(data, (string)parameterOnMethod["type"], rootPath, modelIndexList, controllerPathName);
if (!string.IsNullOrWhiteSpace(modelIndex))
{
@ -225,7 +240,7 @@ namespace Volo.Abp.Cli.Commands
foreach (var parameterItem in parameterModel.OrderBy(p => p.DisplayOrder))
{
var parameterItemModelName = parameterItem.Type.PascalToKebabCase() + ".ts";
var parameterItemModelPath = $"src/app/{rootPath}/shared/models/{parameterItemModelName}";
var parameterItemModelPath = $"{outputPrefix}/{rootPath}/{controllerPathName}/models/{parameterItemModelName}";
if (parameterItem.BindingSourceId == "body" && !File.Exists(parameterItemModelPath))
{
parameterItem.Type = "any";
@ -262,7 +277,7 @@ namespace Volo.Abp.Cli.Commands
var secondType = secondTypeArray[secondTypeArray.Length - 1].TrimEnd('>');
var secondTypeModelName = secondType.PascalToKebabCase() + ".ts";
var secondTypeModelPath = $"src/app/{rootPath}/shared/models/{secondTypeModelName}";
var secondTypeModelPath = $"{outputPrefix}/{rootPath}/{controllerPathName}/models/{secondTypeModelName}";
if (firstType == "List" && !File.Exists(secondTypeModelPath))
{
secondType = "any";
@ -306,7 +321,7 @@ namespace Volo.Abp.Cli.Commands
}
}
var modelIndex = CreateType(data, returnValueType, rootPath, modelIndexList);
var modelIndex = CreateType(data, returnValueType, rootPath, modelIndexList, controllerPathName);
if (!string.IsNullOrWhiteSpace(modelIndex))
{
@ -359,26 +374,31 @@ namespace Volo.Abp.Cli.Commands
serviceFileText.AppendLine("}");
serviceFileText.Replace("[controllerName]", controllerName);
File.WriteAllText($"src/app/{rootPath}/shared/services/{controllerServiceName}", serviceFileText.ToString());
}
File.WriteAllText($"{outputPrefix}/{rootPath}/{controllerPathName}/services/{controllerServiceName}", serviceFileText.ToString());
var serviceIndexFileText = new StringBuilder();
foreach (var serviceIndexItem in serviceIndexList.Distinct())
{
serviceIndexFileText.AppendLine($"export * from './{serviceIndexItem}';");
}
File.WriteAllText($"src/app/{rootPath}/shared/services/index.ts", serviceIndexFileText.ToString());
var serviceIndexFileText = new StringBuilder();
var modelIndexFileText = new StringBuilder();
foreach (var serviceIndexItem in serviceIndexList.Distinct())
{
serviceIndexFileText.AppendLine($"export * from './{serviceIndexItem}';");
}
foreach (var modelIndexItem in modelIndexList.Distinct())
{
modelIndexFileText.AppendLine($"export * from './{modelIndexItem}';");
}
File.WriteAllText($"{outputPrefix}/{rootPath}/{controllerPathName}/services/index.ts", serviceIndexFileText.ToString());
if (modelIndexList.Count > 0)
{
var modelIndexFileText = new StringBuilder();
foreach (var modelIndexItem in modelIndexList.Distinct())
{
modelIndexFileText.AppendLine($"export * from './{modelIndexItem}';");
}
File.WriteAllText($"src/app/{rootPath}/shared/models/index.ts", modelIndexFileText.ToString());
File.WriteAllText($"{outputPrefix}/{rootPath}/{controllerPathName}/models/index.ts", modelIndexFileText.ToString());
}
}
}
Logger.LogInformation("Completed!");
@ -415,7 +435,7 @@ namespace Volo.Abp.Cli.Commands
return moduleList;
}
private static string CreateType(JObject data, string returnValueType, string rootPath, List<string> modelIndexList)
private static string CreateType(JObject data, string returnValueType, string rootPath, List<string> modelIndexList, string controllerPathName)
{
var type = data["types"][returnValueType];
@ -425,30 +445,45 @@ namespace Volo.Abp.Cli.Commands
}
if (returnValueType.StartsWith("Volo.Abp.Application.Dtos")
|| returnValueType.StartsWith("System.Collections")
|| returnValueType == "System.String"
|| returnValueType == "System.Void"
|| returnValueType.Contains("System.Net.HttpStatusCode?")
|| returnValueType.Contains("IActionResult")
|| returnValueType.Contains("ActionResult")
|| returnValueType.Contains("IStringValueType")
|| returnValueType.Contains("IValueValidator")
)
|| returnValueType.StartsWith("System.Collections")
|| returnValueType == "System.String"
|| returnValueType == "System.Void"
|| returnValueType.Contains("System.Net.HttpStatusCode?")
|| returnValueType.Contains("IActionResult")
|| returnValueType.Contains("ActionResult")
|| returnValueType.Contains("IStringValueType")
|| returnValueType.Contains("IValueValidator")
)
{
return null;
if (returnValueType.Contains("<"))
{
returnValueType = returnValueType.Split('<')[1].Split('>')[0];
if (returnValueType.StartsWith("Volo.Abp.Application.Dtos")
|| returnValueType.StartsWith("System.Collections")
|| returnValueType == "System.String"
|| returnValueType == "System.Void"
|| returnValueType.Contains("System.Net.HttpStatusCode?")
|| returnValueType.Contains("IActionResult")
|| returnValueType.Contains("ActionResult")
|| returnValueType.Contains("IStringValueType")
|| returnValueType.Contains("IValueValidator")
)
{
return null;
}
}
else
{
return null;
}
}
var typeNameSplit = returnValueType.Split(".");
var typeName = typeNameSplit[typeNameSplit.Length - 1];
if (typeName.Contains("HttpStatusCode"))
{
}
var typeModelName = typeName.Replace("<", "").Replace(">", "").Replace("?", "").PascalToKebabCase() + ".ts";
var path = $"src/app/{rootPath}/shared/models/{typeModelName}";
var path = $"{outputPrefix}/{rootPath}/{controllerPathName}/models/{typeModelName}";
var modelFileText = new StringBuilder();
@ -481,7 +516,7 @@ namespace Volo.Abp.Cli.Commands
modelFileText.AppendLine($"import {{ {baseTypeName} }} from '{baseTypeKebabCase}';");
extends = "extends " + (!string.IsNullOrWhiteSpace(customBaseTypeName) ? customBaseTypeName : baseTypeName);
var modelIndex = CreateType(data, baseType, rootPath, modelIndexList);
var modelIndex = CreateType(data, baseType, rootPath, modelIndexList, controllerPathName);
if (!string.IsNullOrWhiteSpace(modelIndex))
{
modelIndexList.Add(modelIndex);
@ -514,7 +549,7 @@ namespace Volo.Abp.Cli.Commands
propertyName = (char.ToLower(propertyName[0]) + propertyName.Substring(1));
var typeSimple = (string)property["typeSimple"];
var modelIndex = CreateType(data, (string)property["type"], rootPath, modelIndexList);
var modelIndex = CreateType(data, (string)property["type"], rootPath, modelIndexList, controllerPathName);
if (typeSimple.IndexOf("[") > -1 && typeSimple.IndexOf("]") > -1)
{
@ -547,7 +582,7 @@ namespace Volo.Abp.Cli.Commands
)
{
var typeSimpleModelName = typeSimple.PascalToKebabCase() + ".ts";
var modelPath = $"src/app/{rootPath}/shared/models/{typeSimpleModelName}";
var modelPath = $"{outputPrefix}/{rootPath}/{controllerPathName}/models/{typeSimpleModelName}";
if (!File.Exists(modelPath))
{
typeSimple = "any" + (typeSimple.Contains("[]") ? "[]" : "");
@ -561,10 +596,18 @@ namespace Volo.Abp.Cli.Commands
if (!string.IsNullOrWhiteSpace(modelIndex))
{
var from = "../models";
var propertyTypeSplit = ((string)property["type"]).Split(".");
var propertyType = propertyTypeSplit[propertyTypeSplit.Length - 1];
var propertyTypeKebabCase = propertyType.PascalToKebabCase();
if (File.Exists($"{outputPrefix}/{rootPath}/{controllerPathName}/models/{propertyTypeKebabCase}.ts"))
{
from = "./" + propertyTypeKebabCase;
}
modelFileText.Insert(0, "");
modelFileText.Insert(0, $"import {{ {propertyType} }} from '../models';");
modelFileText.Insert(0, $"import {{ {propertyType} }} from '{from}';");
modelFileText.Insert(0, "");
modelIndexList.Add(modelIndex);
}
@ -608,7 +651,7 @@ namespace Volo.Abp.Cli.Commands
modelFileText.AppendLine("}");
}
File.WriteAllText($"src/app/{rootPath}/shared/models/{typeModelName}", modelFileText.ToString());
File.WriteAllText($"{outputPrefix}/{rootPath}/{controllerPathName}/models/{typeModelName}", modelFileText.ToString());
return typeModelName.Replace(".ts", "");
}

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

@ -199,7 +199,6 @@ namespace Volo.Abp.Cli.ProjectBuilding
}
catch (Exception ex)
{
Console.WriteLine("Error occured while getting the NuGet version from {0} : {1}", url, ex.Message);
return null;
}
}

73
framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/DefaultConventionalRegistrar.cs

@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
@ -24,13 +26,18 @@ namespace Volo.Abp.DependencyInjection
return;
}
var serviceTypes = ExposedServiceExplorer.GetExposedServices(type);
var exposedServiceTypes = ExposedServiceExplorer.GetExposedServices(type);
TriggerServiceExposing(services, type, serviceTypes);
TriggerServiceExposing(services, type, exposedServiceTypes);
foreach (var serviceType in serviceTypes)
foreach (var exposedServiceType in exposedServiceTypes)
{
var serviceDescriptor = ServiceDescriptor.Describe(serviceType, type, lifeTime.Value);
var serviceDescriptor = CreateServiceDescriptor(
type,
exposedServiceType,
exposedServiceTypes,
lifeTime.Value
);
if (dependencyAttribute?.ReplaceServices == true)
{
@ -46,7 +53,63 @@ namespace Volo.Abp.DependencyInjection
}
}
}
protected virtual ServiceDescriptor CreateServiceDescriptor(
Type implementationType,
Type exposingServiceType,
List<Type> allExposingServiceTypes,
ServiceLifetime lifeTime)
{
if (lifeTime.IsIn(ServiceLifetime.Singleton, ServiceLifetime.Scoped))
{
var redirectedType = GetRedirectedTypeOrNull(
implementationType,
exposingServiceType,
allExposingServiceTypes
);
if (redirectedType != null)
{
return ServiceDescriptor.Describe(
exposingServiceType,
provider => provider.GetService(redirectedType),
lifeTime
);
}
}
return ServiceDescriptor.Describe(
exposingServiceType,
implementationType,
lifeTime
);
}
protected virtual Type GetRedirectedTypeOrNull(
Type implementationType,
Type exposingServiceType,
List<Type> allExposingServiceTypes)
{
if (allExposingServiceTypes.Count < 2)
{
return null;
}
if (exposingServiceType == implementationType)
{
return null;
}
if (allExposingServiceTypes.Contains(implementationType))
{
return implementationType;
}
return allExposingServiceTypes.FirstOrDefault(
t => t != exposingServiceType && exposingServiceType.IsAssignableFrom(t)
);
}
protected virtual DependencyAttribute GetDependencyAttributeOrNull(Type type)
{
return type.GetCustomAttribute<DependencyAttribute>(true);

6
framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Localization/Resources/AbpDdd/de.json

@ -0,0 +1,6 @@
{
"culture": "de",
"texts": {
"MaxResultCountExceededExceptionMessage": "{0} kann nicht mehr als {1} sein! Erhöhen Sie {2}.{3} auf der Serverseite, um mehr Ergebnisse zu ermöglichen."
}
}

6
framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Localization/Resources/AbpDdd/nl.json

@ -0,0 +1,6 @@
{
"culture": "nl",
"texts": {
"MaxResultCountExceededExceptionMessage": "{0} kan niet meer dan {1} zijn! Vergroot {2}.{3} op de server om een groter resultaat toe te staan."
}
}

23
framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/de.json

@ -0,0 +1,23 @@
{
"culture": "de",
"texts": {
"DisplayName:Abp.Mailing.DefaultFromAddress": "Standard-Absenderadresse",
"DisplayName:Abp.Mailing.DefaultFromDisplayName": "Standard-Absendername",
"DisplayName:Abp.Mailing.Smtp.Host": "Host",
"DisplayName:Abp.Mailing.Smtp.Port": "Port",
"DisplayName:Abp.Mailing.Smtp.UserName": "Benutzername",
"DisplayName:Abp.Mailing.Smtp.Password": "Passwort",
"DisplayName:Abp.Mailing.Smtp.Domain": "Domain",
"DisplayName:Abp.Mailing.Smtp.EnableSsl": "SSL aktivieren",
"DisplayName:Abp.Mailing.Smtp.UseDefaultCredentials": "Standard-Anmeldeinformationen verwenden",
"Description:Abp.Mailing.DefaultFromAddress": "Die Standard-Absenderadresse",
"Description:Abp.Mailing.DefaultFromDisplayName": "Der Standard-Absendername",
"Description:Abp.Mailing.Smtp.Host": "Der Name oder die IP-Adresse des für SMTP-Transaktionen verwendeten Hosts.",
"Description:Abp.Mailing.Smtp.Port": "Der für SMTP-Transaktionen verwendete Port.",
"Description:Abp.Mailing.Smtp.UserName": "Benutzername, der mit den Anmeldedaten verknüpft ist.",
"Description:Abp.Mailing.Smtp.Password": "Das Passwort für den Benutzernamen, der mit den Anmeldeinformationen verknüpft ist.",
"Description:Abp.Mailing.Smtp.Domain": "Die Domäne oder der Computername, der die Anmeldeinformationen verifiziert.",
"Description:Abp.Mailing.Smtp.EnableSsl": "Bestimmt, ob der SmptClient Secure Sockets Layer (SSL) zur Verschlüsselung der Verbindung verwendet.",
"Description:Abp.Mailing.Smtp.UseDefaultCredentials": "Bestimmt, ob die DefaultCredentials mit Anfragen gesendet werden."
}
}

23
framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/nl.json

@ -0,0 +1,23 @@
{
"culture": "nl",
"texts": {
"DisplayName:Abp.Mailing.DefaultFromAddress": "Standard vanaf adres",
"DisplayName:Abp.Mailing.DefaultFromDisplayName": "Standaard vanaf weergave naam",
"DisplayName:Abp.Mailing.Smtp.Host": "Host",
"DisplayName:Abp.Mailing.Smtp.Port": "Poort",
"DisplayName:Abp.Mailing.Smtp.UserName": "Gebruiker naam",
"DisplayName:Abp.Mailing.Smtp.Password": "wachtwoord",
"DisplayName:Abp.Mailing.Smtp.Domain": "Domein",
"DisplayName:Abp.Mailing.Smtp.EnableSsl": "SSL toestaan",
"DisplayName:Abp.Mailing.Smtp.UseDefaultCredentials": "Gebruik standaard inloggegevens",
"Description:Abp.Mailing.DefaultFromAddress": "Standard vanaf adres",
"Description:Abp.Mailing.DefaultFromDisplayName": "Standaard vanaf weergave naam",
"Description:Abp.Mailing.Smtp.Host": "De naam of het IP-adres van de host die wordt gebruikt voor SMTP-transacties.",
"Description:Abp.Mailing.Smtp.Port": "De poort die wordt gebruikt voor SMTP-transacties.",
"Description:Abp.Mailing.Smtp.UserName": "Gebruikersnaam gekoppeld aan de inloggegevens.",
"Description:Abp.Mailing.Smtp.Password": "Het wachtwoord voor de gebruikersnaam die bij de inloggegevens hoort.",
"Description:Abp.Mailing.Smtp.Domain": "Het domein of de computernaam die de inloggegevens verifieert.",
"Description:Abp.Mailing.Smtp.EnableSsl": "Of de SmtpClient Secure Sockets Layer (SSL) gebruikt om de verbinding te versleutelen.",
"Description:Abp.Mailing.Smtp.UseDefaultCredentials": "Of de standaard inloggegevens worden verzonden met verzoeken."
}
}

7
framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Resources/AbpLocalization/de.json

@ -0,0 +1,7 @@
{
"culture": "de",
"texts": {
"DisplayName:Abp.Localization.DefaultLanguage": "Standardsprache",
"Description:Abp.Localization.DefaultLanguage": "Die Standardsprache der Anwendung."
}
}

7
framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Resources/AbpLocalization/nl.json

@ -0,0 +1,7 @@
{
"culture": "nl",
"texts": {
"DisplayName:Abp.Localization.DefaultLanguage": "Standaard taal",
"Description:Abp.Localization.DefaultLanguage": "De standaardtaal van de applicatie."
}
}

6
framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/de.json

@ -0,0 +1,6 @@
{
"culture": "de",
"texts": {
"Menu:Administration": "Administration"
}
}

6
framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/nl.json

@ -0,0 +1,6 @@
{
"culture": "nl",
"texts": {
"Menu:Administration": "Administratie"
}
}

63
framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/de.json

@ -0,0 +1,63 @@
{
"culture": "de",
"texts": {
"InternalServerErrorMessage": "Während Ihrer Anfrage ist ein interner Fehler aufgetreten!",
"ValidationErrorMessage": "Ihre Anfrage ist nicht gültig!",
"ValidationNarrativeErrorMessageTitle": "Die folgenden Fehler wurden bei der Validierung entdeckt.",
"DefaultErrorMessage": "Ein Fehler ist aufgetreten!",
"DefaultErrorMessageDetail": "Es wurden keine Fehlerdetails vom Server gesendet.",
"DefaultErrorMessage401": "Sie sind nicht authentifiziert.",
"DefaultErrorMessage401Detail": "Sie sollten sich anmelden, um diese Operation durchzuführen.",
"DefaultErrorMessage403": "Sie sind nicht autorisiert!",
"DefaultErrorMessage403Detail": "Es ist Ihnen nicht erlaubt, diese Operation durchzuführen!",
"DefaultErrorMessage404": "Ressource nicht gefunden!",
"DefaultErrorMessage404Detail": "Die angeforderte Ressource konnte auf dem Server nicht gefunden werden!",
"EntityNotFoundErrorMessage": "Es gibt keine Entität {0} mit id = {1}!",
"Languages": "Sprachen",
"Error": "Fehler",
"AreYouSure": "Sind Sie sicher?",
"Cancel": "Abbrechen",
"Yes": "Ja",
"No": "Nein",
"Ok": "Ok",
"Close": "Schließen",
"Save": "Speichern",
"SavingWithThreeDot": "Speichere...",
"Actions": "Aktionen",
"Delete": "Löschen",
"Edit": "Bearbeiten",
"Refresh": "Aktualisieren",
"Language": "Sprache",
"LoadMore": "Mehr laden",
"ProcessingWithThreeDot": "Verarbeite...",
"LoadingWithThreeDot": "Lade...",
"Welcome": "Willkommen",
"Login": "Anmelden",
"Register": "Registrieren",
"Logout": "Abmelden",
"Submit": "Absenden",
"Back": "Zurück",
"PagerSearch": "Suchen",
"PagerNext": "Nächste",
"PagerPrevious": "Vorherige",
"PagerFirst": "Erste",
"PagerLast": "Letzte",
"PagerInfo": "Zeige _START_ bis _END_ von _TOTAL_ Einträgen",
"PagerInfo{0}{1}{2}": "Zeige {0} bis {1} von {2} Einträgen",
"PagerInfoEmpty": "Zeige 0 bis 0 von 0 Einträgen",
"PagerInfoFiltered": "(gefiltert von _MAX_ Einträgen insgesamt)",
"NoDataAvailableInDatatable": "Keine Daten verfügbar",
"PagerShowMenuEntries": "Zeige _MENU_ Einträge",
"DatatableActionDropdownDefaultText": "Aktionen",
"ChangePassword": "Passwort ändern",
"PersonalInfo": "Mein Profil",
"AreYouSureYouWantToCancelEditingWarningMessage": "Sie haben ungespeicherte Änderungen.",
"UnhandledException": "Unerwartete Ausnahme!",
"401Message": "Unauthorisiert",
"403Message": "Verboten",
"404Message": "Seite nicht gefunden",
"500Message": "Internet Server Fehler",
"GoHomePage": "Zur Startseite",
"GoBack": "Zurück"
}
}

63
framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/nl.json

@ -0,0 +1,63 @@
{
"culture": "nl",
"texts": {
"InternalServerErrorMessage": "Er is een interne fout opgetreden tijdens uw verzoek!",
"ValidationErrorMessage": "Uw verzoek is niet geldig!",
"ValidationNarrativeErrorMessageTitle": "Tijdens de validatie zijn de volgende fouten gedetecteerd.",
"DefaultErrorMessage": "er is een fout opgetreden!",
"DefaultErrorMessageDetail": "Foutdetails niet verzonden door server.",
"DefaultErrorMessage401": "U bent niet geverifieerd!",
"DefaultErrorMessage401Detail": "U moet inloggen om deze bewerking uit te voeren.",
"DefaultErrorMessage403": "U bent niet geautoriseerd!",
"DefaultErrorMessage403Detail": "U mag deze bewerking niet uitvoeren!",
"DefaultErrorMessage404": "Bron niet gevonden!",
"DefaultErrorMessage404Detail": "De gevraagde bron kan niet worden gevonden op de server!",
"EntityNotFoundErrorMessage": "Er is geen entiteit {0} met id = {1}!",
"Languages": "Talen",
"Error": "Fout",
"AreYouSure": "Bent u zeker?",
"Cancel": "Annuleren",
"Yes": "Ja",
"No": "Nee",
"Ok": "Ok",
"Close": "Sluiten",
"Save": "Opslaan",
"SavingWithThreeDot": "Opslaan...",
"Actions": "Acties",
"Delete": "Verwijder",
"Edit": "Bewerk",
"Refresh": "Ververs",
"Language": "Taal",
"LoadMore": "Meer laden",
"ProcessingWithThreeDot": "Verwerken...",
"LoadingWithThreeDot": "Laden...",
"Welcome": "Welkom",
"Login": "Log in",
"Register": "Registreren",
"Logout": "Afmelden",
"Submit": "Verzenden",
"Back": "Terug",
"PagerSearch": "Zoeken",
"PagerNext": "Volgende",
"PagerPrevious": "Vorige",
"PagerFirst": "Eerste",
"PagerLast": "Laatste",
"PagerInfo": "Toont _START_ tot _END_ van _TOTAL_ vermeldingen",
"PagerInfo{0}{1}{2}": "{0} tot {1} van {2} vermeldingen weergeven",
"PagerInfoEmpty": "Toont 0 tot 0 van 0 vermeldingen",
"PagerInfoFiltered": "(gefilterd uit in totaal _MAX_ vermeldingen)",
"NoDataAvailableInDatatable": "Geen gegevens beschikbaar",
"PagerShowMenuEntries": "Toon _MENU_-vermeldingen",
"DatatableActionDropdownDefaultText": "Acties",
"ChangePassword": "Verander wachtwoord",
"PersonalInfo": "Mijn profiel",
"AreYouSureYouWantToCancelEditingWarningMessage": "U heeft nog niet-opgeslagen wijzigingen.",
"UnhandledException": "Onverwerkte uitzondering!",
"401Message": "Ongeautoriseerd",
"403Message": "Verboden",
"404Message": "Pagina niet gevonden",
"500Message": "Interne Server Fout",
"GoHomePage": "Ga naar de homepage",
"GoBack": "Ga terug"
}
}

2
framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/cs.json

@ -20,7 +20,7 @@
"ThisFieldIsNotValid.": "{0} není platný.",
"ThisFieldIsNotAValidEmailAddress.": "V poli {0} není platný email.",
"ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "Pole přijímá soubory pouze s následujícími koncovkami: {0}",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthoOf{0}": "Vy poli musí být řežezec nebo řada o maximální délce '{0}'.",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "Vy poli musí být řežezec nebo řada o maximální délce '{0}'.",
"ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "V poli musí být řežezec nebo řada o minimální délce '{0}'.",
"ThisFieldIsNotAValidPhoneNumber.": "V poli není platné telefonní číslo.",
"ThisFieldMustBeBetween{0}And{1}": "Pole musí být mezi {0} a {1}.",

34
framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/de.json

@ -0,0 +1,34 @@
{
"culture": "de",
"texts": {
"'{0}' and '{1}' do not match.": "'{0}' und '{1}' stimmen nicht überein.",
"The {0} field is not a valid credit card number.": "Das Feld {0} ist keine gültige Kreditkartennummer.",
"{0} is not valid.": "{0} ist nicht gültig.",
"The {0} field is not a valid e-mail address.": "Das Feld {0} ist keine gültige E-Mail-Adresse.",
"The {0} field only accepts files with the following extensions: {1}": "Das Feld {0} akzeptiert nur Dateien mit den folgenden Erweiterungen: {1}",
"The field {0} must be a string or array type with a maximum length of '{1}'.": "Das Feld {0} muss eine Zeichenfolge oder Auflistung mit einer maximalen Länge von '{1}' sein.",
"The field {0} must be a string or array type with a minimum length of '{1}'.": "Das Feld {0} muss eine Zeichenfolge oder Auflistung mit einer Mindestlänge von '{1}' sein.",
"The {0} field is not a valid phone number.": "Das Feld {0} ist keine gültige Telefonnummer.",
"The field {0} must be between {1} and {2}.": "Das Feld {0} muss zwischen {1} und {2} liegen.",
"The field {0} must match the regular expression '{1}'.": "Das Feld {0} muss dem regulären Ausdruck '{1}' entsprechen.",
"The {0} field is required.": "Das Feld {0} ist erforderlich.",
"The field {0} must be a string with a maximum length of {1}.": "Das Feld {0} muss eine Zeichenfolge mit einer maximalen Länge von {1} sein.",
"The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "Das Feld {0} muss eine Zeichenfolge mit einer minimalen Länge von {2} und einer maximalen Länge von {1} sein.",
"The {0} field is not a valid fully-qualified http, https, or ftp URL.": "Das {0}-Feld ist keine gültige vollqualifizierte http-, https- oder ftp-URL.",
"The field {0} is invalid.": "Das Feld {0} ist ungültig.",
"ThisFieldIsNotAValidCreditCardNumber.": "Dieses Feld ist keine gültige Kreditkartennummer.",
"ThisFieldIsNotValid.": "Dieses Feld ist nicht gültig.",
"ThisFieldIsNotAValidEmailAddress.": "Dieses Feld ist keine gültige E-Mail-Adresse.",
"ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "Dieses Feld akzeptiert nur Dateien mit den folgenden Erweiterungen: {0}",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "Dieses Feld muss eine Zeichenfolge oder Auflistung mit einer maximalen Länge von '{0}' sein.",
"ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "Dieses Feld muss eine Zeichenfolge oder Auflistung mit einer Mindestlänge von '{0}' sein.",
"ThisFieldIsNotAValidPhoneNumber.": "Dieses Feld ist keine gültige Telefonnummer.",
"ThisFieldMustBeBetween{0}And{1}": "Dieses Feld muss zwischen {0} und {1} liegen.",
"ThisFieldMustMatchTheRegularExpression{0}": "Dieses Feld muss dem regulären Ausdruck '{0}' entsprechen.",
"ThisFieldIsRequired.": "Dieses Feld ist erforderlich.",
"ThisFieldMustBeAStringWithAMaximumLengthOf{0}": "Dieses Feld muss eine Zeichenfolge mit einer maximalen Länge von {0} sein.",
"ThisFieldMustBeAStringWithAMinimumLengthOf{1}AndAMaximumLengthOf{0}": "Dieses Feld muss eine Zeichenfolge mit einer Mindestlänge von '{0}' sein.",
"ThisFieldIsNotAValidFullyQualifiedHttpHttpsOrFtpUrl": "Dieses Feld ist keine gültige vollqualifizierte http-, https- oder ftp-URL.",
"ThisFieldIsInvalid.": "Dieses Feld ist ungültig."
}
}

2
framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/en.json

@ -20,7 +20,7 @@
"ThisFieldIsNotValid.": "This field is not valid.",
"ThisFieldIsNotAValidEmailAddress.": "This field is not a valid e-mail address.",
"ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "This field only accepts files with the following extensions: {0}",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthoOf{0}": "This field must be a string or array type with a maximum length of '{0}'.",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "This field must be a string or array type with a maximum length of '{0}'.",
"ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "This field must be a string or array type with a minimum length of '{0}'.",
"ThisFieldIsNotAValidPhoneNumber.": "This field is not a valid phone number.",
"ThisFieldMustBeBetween{0}And{1}": "This field must be between {0} and {1}.",

2
framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/es.json

@ -20,7 +20,7 @@
"ThisFieldIsNotValid.": "Este campo no es válido.",
"ThisFieldIsNotAValidEmailAddress.": "Este campo no es una dirección de correo electrónico válida.",
"ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "Este campo sólo acepta archivos con las siguientes extensiones: {0}",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthoOf{0}": "Este campo debe ser una cadena o un array con una longitud máxima de '{0}'.",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "Este campo debe ser una cadena o un array con una longitud máxima de '{0}'.",
"ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "Este campo debe ser una cadena o un array con una longitud mínima de '{0}'.",
"ThisFieldIsNotAValidPhoneNumber.": "Este campo no es un número de teléfono válido.",
"ThisFieldMustBeBetween{0}And{1}": "Este campo debe tener un valor entre {0} y {1}.",

34
framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/nl.json

@ -0,0 +1,34 @@
{
"culture": "nl",
"texts": {
"'{0}' and '{1}' do not match.": "'{0}' en '{1}' komen niet overeen.",
"The {0} field is not a valid credit card number.": "Het veld {0} is geen geldig krediet kaartnummer.",
"{0} is not valid.": "{0} is niet geldig.",
"The {0} field is not a valid e-mail address.": "Het veld {0} is geen geldig e-mailadres.",
"The {0} field only accepts files with the following extensions: {1}": "Het veld {0} accepteert alleen bestanden met de volgende extensies: {1}",
"The field {0} must be a string or array type with a maximum length of '{1}'.": "Het veld {0} moet een tekenreeks- of arraytype zijn met een maximale lengte van '{1}'.",
"The field {0} must be a string or array type with a minimum length of '{1}'.": "Het veld {0} moet een tekenreeks- of arraytype zijn met een minimale lengte van '{1}'.",
"The {0} field is not a valid phone number.": "Het veld {0} is geen geldig telefoonnummer.",
"The field {0} must be between {1} and {2}.": "Het veld {0} moet tussen {1} en {2} liggen.",
"The field {0} must match the regular expression '{1}'.": "Het veld {0} moet overeenkomen met de reguliere expressie '{1}'.",
"The {0} field is required.": "Het veld {0} is verplicht.",
"The field {0} must be a string with a maximum length of {1}.": "Het veld {0} moet een tekenreeks zijn met een maximale lengte van {1}.",
"The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "Het veld {0} moet een tekenreeks zijn met een minimale lengte van {2} en een maximale lengte van {1}.",
"The {0} field is not a valid fully-qualified http, https, or ftp URL.": "Het veld {0} is geen geldige, volledig gekwalificeerde http-, https- of ftp-URL.",
"The field {0} is invalid.": "Het veld {0} is ongeldig.",
"ThisFieldIsNotAValidCreditCardNumber.": "Dit veld is geen geldig krediet kaartnummer.",
"ThisFieldIsNotValid.": "Dir veld is ongeldig.",
"ThisFieldIsNotAValidEmailAddress.": "Dit veld is geen geldig e-mail adres.",
"ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "Dit veld accepteert alleen bestanden met de volgende extensies: {0}",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "Dit veld moet een tekenreeks- of arraytype zijn met een maximale lengte van '{0}'.",
"ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "Dit veld moet een tekenreeks- of arraytype zijn met een minimale lengte van '{0}'.",
"ThisFieldIsNotAValidPhoneNumber.": "Dit veld is geen geldig telefoonnummer.",
"ThisFieldMustBeBetween{0}And{1}": "Dit veld moet tussen {0} en {1} liggen.",
"ThisFieldMustMatchTheRegularExpression{0}": "Dit veld moet overeenkomen met de reguliere expressie '{0}'.",
"ThisFieldIsRequired.": "Dit veld is verplicht.",
"ThisFieldMustBeAStringWithAMaximumLengthOf{0}": "Dit veld moet een tekenreeks zijn met een maximale lengte van {0}.",
"ThisFieldMustBeAStringWithAMinimumLengthOf{1}AndAMaximumLengthOf{0}": "Dit veld moet een tekenreeks zijn met een minimale lengte van {1} en een maximale lengte van {0}.",
"ThisFieldIsNotAValidFullyQualifiedHttpHttpsOrFtpUrl": "Dit veld is geen geldige, volledig gekwalificeerde http-, https- of ftp-URL.",
"ThisFieldIsInvalid.": "Dit veld is ongeldig."
}
}

2
framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/pt-BR.json

@ -20,7 +20,7 @@
"ThisFieldIsNotValid.": "Campo inválido.",
"ThisFieldIsNotAValidEmailAddress.": "E-mail inválido.",
"ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "Este campo só aceita arquivos com asseguintes extensões: {0}",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthoOf{0}": "This field must be a string or array type with a maximum length of '{0}'.",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "This field must be a string or array type with a maximum length of '{0}'.",
"ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "This field must be a string or array type with a minimum length of '{0}'.",
"ThisFieldIsNotAValidPhoneNumber.": "Número de telefone inválido.",
"ThisFieldMustBeBetween{0}And{1}": "Este campo deve estar entre {0} e {1}.",

2
framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/ru.json

@ -20,7 +20,7 @@
"ThisFieldIsNotValid.": "Значение в этом поле недействительно.",
"ThisFieldIsNotAValidEmailAddress.": "Это поле не содержит действительный адрес электронной почты.",
"ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "Вы можете загрузить файлы только следующих форматов: {0}",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthoOf{0}": "Это поле должно иметь тип строки или массива с максимальной длиной '{0}'.",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "Это поле должно иметь тип строки или массива с максимальной длиной '{0}'.",
"ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "Это поле должно иметь тип строки или массива с минимальной длиной '{0}'.",
"ThisFieldIsNotAValidPhoneNumber.": "Это поле не содержит действительный номер телефона.",
"ThisFieldMustBeBetween{0}And{1}": "Это поле должно быть между {0} и {1}.",

2
framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/tr.json

@ -20,7 +20,7 @@
"ThisFieldIsNotValid.": "Bu alan geçerli değil.",
"ThisFieldIsNotAValidEmailAddress.": "Bu alan geçerli bir e-posta adresi olmalıdır.",
"ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "Bu alan sadece şu uzantılarda dosyaları kabul eder: {0}",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthoOf{0}": "Bu alan en fazla '{0}' uzunluğunda bir metin ya da dizi olmalıdır.",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "Bu alan en fazla '{0}' uzunluğunda bir metin ya da dizi olmalıdır.",
"ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "Bu alan en az '{0}' uzunluğunda bir metin ya da dizi olmalıdır.",
"ThisFieldIsNotAValidPhoneNumber.": "Bu alan geçerli bir telefon numarası olmalıdır.",
"ThisFieldMustBeBetween{0}And{1}": "Bu alanın değeri {0} ile {1} arasında olmalıdır.",

2
framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/zh-Hans.json

@ -20,7 +20,7 @@
"ThisFieldIsNotValid.": "验证未通过.",
"ThisFieldIsNotAValidEmailAddress.": "字段不是有效的邮箱地址.",
"ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "字段只允许以下扩展名的文件: {0}",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthoOf{0}": "字段必须是最大长度为'{0}'的字符串或数组.",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "字段必须是最大长度为'{0}'的字符串或数组.",
"ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "字段必须是最小长度为'{0}'的字符串或数组.",
"ThisFieldIsNotAValidPhoneNumber.": "字段不是有效的手机号码.",
"ThisFieldMustBeBetween{0}And{1}": "字段值必须在{0}和{1}范围内.",

2
framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/zh-Hant.json

@ -20,7 +20,7 @@
"ThisFieldIsNotValid.": "此驗證未通過.",
"ThisFieldIsNotAValidEmailAddress.": "此欄位不是有效的郵箱地址.",
"ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "此欄位只允許以下副檔名的文件: {0}",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthoOf{0}": "此欄位必須是最大長度為'{0}'的字串或陣列.",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "此欄位必須是最大長度為'{0}'的字串或陣列.",
"ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "此欄位必須是最小長度為'{0}'的字串或陣列.",
"ThisFieldIsNotAValidPhoneNumber.": "此欄位不是有效的電話號碼.",
"ThisFieldMustBeBetween{0}And{1}": "此欄位值必須在{0}和{1}範圍內.",

4
framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/DictionaryBasedFileProvider.cs

@ -11,7 +11,7 @@ namespace Volo.Abp.VirtualFileSystem
public virtual IFileInfo GetFileInfo(string subpath)
{
if (string.IsNullOrEmpty(subpath))
if (subpath == null)
{
return new NotFoundFileInfo(subpath);
}
@ -67,4 +67,4 @@ namespace Volo.Abp.VirtualFileSystem
return subpath;
}
}
}
}

9
framework/src/Volo.Abp.VirtualFileSystem/Volo/Abp/VirtualFileSystem/VirtualFilePathHelper.cs

@ -10,6 +10,11 @@ namespace Volo.Abp.VirtualFileSystem
public static string NormalizePath(string fullPath)
{
if (fullPath.Equals("/", StringComparison.Ordinal))
{
return string.Empty;
}
var fileName = fullPath;
var extension = "";
@ -29,7 +34,7 @@ namespace Volo.Abp.VirtualFileSystem
return NormalizeChars(fileName) + extension;
}
private static string NormalizeChars(string fileName)
{
var folderParts = fileName.Replace(".", "/").Split("/");
@ -42,4 +47,4 @@ namespace Volo.Abp.VirtualFileSystem
return folderParts.Take(folderParts.Length - 1).Select(s => s.Replace("-", "_")).JoinAsString("/") + "/" + folderParts.Last();
}
}
}
}

7
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/de.json

@ -0,0 +1,7 @@
{
"culture": "de",
"texts": {
"BirthDate": "Geburtsdatum",
"Value1": "Wert Eins"
}
}

7
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/nl.json

@ -0,0 +1,7 @@
{
"culture": "nl",
"texts": {
"BirthDate": "Geboortedatum",
"Value1": "Waarde een"
}
}

22
framework/test/Volo.Abp.Core.Tests/Microsoft/Extensions/DependencyInjection/DependencyInjection_Tests.cs

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using Shouldly;
using Volo.Abp;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Modularity;
using Volo.Abp.Testing;
@ -63,6 +62,15 @@ namespace Microsoft.Extensions.DependencyInjection
GetRequiredService<ConcreteGenericServiceWithPropertyInject>().ProperyInjectedService.ShouldNotBeNull();
}
[Fact]
public void Singletons_Exposing_Multiple_Services_Should_Returns_The_Same_Instance()
{
var objectByInterfaceRef = GetRequiredService<IMySingletonExposingMultipleServices>();
var objectByClassRef = GetRequiredService<MySingletonExposingMultipleServices>();
ReferenceEquals(objectByInterfaceRef, objectByClassRef).ShouldBeTrue();
}
public class MySingletonService : ISingletonDependency
{
public List<MyEmptyTransientService> TransientInstances { get; }
@ -116,6 +124,17 @@ namespace Microsoft.Extensions.DependencyInjection
}
}
public interface IMySingletonExposingMultipleServices
{
}
[ExposeServices(typeof(IMySingletonExposingMultipleServices), typeof(MySingletonExposingMultipleServices))]
public class MySingletonExposingMultipleServices : IMySingletonExposingMultipleServices, ISingletonDependency
{
}
public class TestModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
@ -124,6 +143,7 @@ namespace Microsoft.Extensions.DependencyInjection
context.Services.AddType<MyTransientService1>();
context.Services.AddType<MyEmptyTransientService>();
context.Services.AddType<ServiceWithPropertyInject>();
context.Services.AddType<MySingletonExposingMultipleServices>();
context.Services.AddTransient(typeof(GenericServiceWithPropertyInject<>));
context.Services.AddTransient(typeof(ConcreteGenericServiceWithPropertyInject));
}

6
framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/de.json

@ -0,0 +1,6 @@
{
"culture": "de",
"texts": {
"hello": "Hallo"
}
}

6
framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/nl.json

@ -0,0 +1,6 @@
{
"culture": "nl",
"texts": {
"hello": "hallo"
}
}

10
framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalization_Tests.cs

@ -73,6 +73,11 @@ namespace Volo.Abp.Localization
_localizer["Car"].Value.ShouldBe("Auto");
}
using (CultureHelper.Use("de"))
{
_localizer["Car"].Value.ShouldBe("Auto");
}
}
[Fact]
@ -98,6 +103,11 @@ namespace Volo.Abp.Localization
_localizer["SeeYou"].Value.ShouldBe("Nos vemos");
}
using (CultureHelper.Use("de"))
{
_localizer["SeeYou"].Value.ShouldBe("Bis bald");
}
}
[Fact]

7
framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/de.json

@ -0,0 +1,7 @@
{
"culture": "de",
"texts": {
"USA": "Vereinigte Staaten von Amerika",
"Brazil": "Brasilien"
}
}

7
framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/nl.json

@ -0,0 +1,7 @@
{
"culture": "nl",
"texts": {
"USA": "Verenigde Staten van Amerika",
"Brazil": "Brazilië"
}
}

7
framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/de.json

@ -0,0 +1,7 @@
{
"culture": "de",
"texts": {
"ThisFieldIsRequired": "Dieses Feld ist ein Pflichtfeld",
"MaxLenghtErrorMessage": "Die Länge dieses Feldes kann maximal '{0}'-Zeichen betragen"
}
}

7
framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/nl.json

@ -0,0 +1,7 @@
{
"culture": "nl",
"texts": {
"ThisFieldIsRequired": "Dit veld is verplicht",
"MaxLenghtErrorMessage": "Dit veld mag maximaal '{0}' tekens bevatten"
}
}

11
framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/de.json

@ -0,0 +1,11 @@
{
"culture": "de",
"texts": {
"Hello <b>{0}</b>.": "Hallo <b>{0}</b>.",
"Car": "Auto",
"CarPlural": "Autos",
"MaxLenghtErrorMessage": "Die Länge dieses Feldes kann maximal '{0}'-Zeichen betragen",
"Universe": "Universum",
"FortyTwo": "Zweiundvierzig"
}
}

11
framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/nl.json

@ -0,0 +1,11 @@
{
"culture": "nl",
"texts": {
"Hello <b>{0}</b>.": "Hallo <b>{0}</b>.",
"Car": "Auto",
"CarPlural": "Auto's",
"MaxLenghtErrorMessage": "De lengte van dit veld mag maximaal '{0}' tekens zijn",
"Universe": "Universum",
"FortyTwo": "Tweeënveertig"
}
}

8
framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/de.json

@ -0,0 +1,8 @@
{
"culture": "de",
"texts": {
"Hello <b>{0}</b>.": "Hallo <b>{0}</b>.",
"Car": "Auto",
"SeeYou": "Bis bald"
}
}

6
framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/nl.json

@ -0,0 +1,6 @@
{
"culture": "nl",
"texts": {
"SeeYou": "Tot ziens"
}
}

18
framework/test/Volo.Abp.VirtualFileSystem.Tests/Volo/Abp/VirtualFileSystem/VirtualFileProvider_Tests.cs

@ -1,4 +1,5 @@
using System.IO;
using System;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.Extensions.DependencyInjection;
@ -47,6 +48,21 @@ namespace Volo.Abp.VirtualFileSystem
contentList.ShouldContain(x => x.Name == "jquery-3-1-1-min.js");
}
[Theory]
[InlineData("/")]
[InlineData("")]
public void Should_Define_And_Get_Embedded_Root_Directory_Contents(string path)
{
//Act
var contents = _virtualFileProvider.GetDirectoryContents(path);
//Assert
contents.Exists.ShouldNotBeNull();
var contentList = contents.ToList();
contentList.ShouldContain(x => x.Name == "js");
}
[DependsOn(typeof(AbpVirtualFileSystemModule))]
public class TestModule : AbpModule
{

45
modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/de.json

@ -0,0 +1,45 @@
{
"culture": "de",
"texts": {
"UserName": "Benutzername",
"EmailAddress": "E-Mail-Adresse",
"UserNameOrEmailAddress": "Benutzername oder E-Mail-Adresse",
"Password": "Passwort",
"RememberMe": "Angemeldet bleiben",
"UseAnotherServiceToLogin": "Einen anderen Dienst zum Anmelden verwenden",
"UserLockedOutMessage": "Das Benutzerkonto wurde aufgrund fehlgeschlagener Anmeldeversuche gesperrt. Bitte warten Sie eine Weile und versuchen Sie es erneut.",
"InvalidUserNameOrPassword": "Ungültiger Benutzername oder Passwort!",
"LoginIsNotAllowed": "Sie dürfen sich nicht anmelden! Sie müssen Ihre E-Mail/Telefonnummer bestätigen.",
"SelfRegistrationDisabledMessage": "Die Selbstregistrierung ist für diese Anwendung deaktiviert. Bitte wenden Sie sich an den Anwendungsadministrator, um einen neuen Benutzer zu registrieren.",
"LocalLoginDisabledMessage": "Die lokale Anmeldung ist für diese Anwendung deaktiviert.",
"Login": "Anmelden",
"Cancel": "Abbrechen",
"Register": "Registrieren",
"AreYouANewUser": "Neuer Benutzer?",
"AlreadyRegistered": "Bereits registriert?",
"InvalidLoginRequest": "Ungültige Login-Anfrage",
"ThereAreNoLoginSchemesConfiguredForThisClient": "Es sind keine Anmeldeschemata für diesen Client konfiguriert.",
"LogInUsingYourProviderAccount": "Melden Sie sich mit Ihrem {0}-Konto an",
"DisplayName:CurrentPassword": "Aktuelles Passwort",
"DisplayName:NewPassword": "Neues Passwort",
"DisplayName:NewPasswordConfirm": "Neues Passwort bestätigen",
"PasswordChangedMessage": "Ihr Passwort wurde erfolgreich geändert.",
"DisplayName:UserName": "Benutzername",
"DisplayName:Email": "E-Mail",
"DisplayName:Name": "Name",
"DisplayName:Surname": "Nachname",
"DisplayName:Password": "Passwort",
"DisplayName:EmailAddress": "E-Mail-Adresse",
"DisplayName:PhoneNumber": "Telefonnummer",
"PersonalSettings": "Persönliche Einstellungen",
"PersonalSettingsSaved": "Persönliche Einstellungen gespeichert",
"PasswordChanged": "Passwort geändert",
"NewPasswordConfirmFailed": "Bitte bestätigen Sie das neue Passwort.",
"Manage": "Verwalten",
"ManageYourProfile": "Ihr profil verwalten",
"DisplayName:Abp.Account.IsSelfRegistrationEnabled": "Ist die Selbstregistrierung aktiviert",
"Description:Abp.Account.IsSelfRegistrationEnabled": "Gibt an, ob ein Benutzer das Konto selbst registrieren kann.",
"DisplayName:Abp.Account.EnableLocalLogin": "Authentifizierung mit einem lokalen Konto",
"Description:Abp.Account.EnableLocalLogin": "Gibt an, ob der Server Benutzern die Authentifizierung mit einem lokalen Konto erlaubt."
}
}

45
modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/nl.json

@ -0,0 +1,45 @@
{
"culture": "nl",
"texts": {
"UserName": "Gebruikersnaam",
"EmailAddress": "E-mailadres",
"UserNameOrEmailAddress": "Gebruikersnaam of e-mail adres",
"Password": "Wachtwoord",
"RememberMe": "Herinner me",
"UseAnotherServiceToLogin": "Gebruik een andere dienst om in te loggen",
"UserLockedOutMessage": "Het gebruikersaccount is geblokkeerd vanwege ongeldige inlogpogingen. Wacht even en probeer het opnieuw.",
"InvalidUserNameOrPassword": "Ongeldige gebruikersnaam of wachtwoord!",
"LoginIsNotAllowed": "U mag niet inloggen! U moet uw e-mailadres / telefoonnummer bevestigen.",
"SelfRegistrationDisabledMessage": "Zelfregistratie is uitgeschakeld voor deze applicatie. Neem contact op met de applicatiebeheerder om een nieuwe gebruiker te registreren.",
"LocalLoginDisabledMessage": "Lokale aanmelding is uitgeschakeld voor deze applicatie.",
"Login": "Log in",
"Cancel": "Annuleer",
"Register": "Registreer",
"AreYouANewUser": "Bent u een nieuwe gebruiker?",
"AlreadyRegistered": "Al geregistreerd?",
"InvalidLoginRequest": "Ongeldig inlogverzoek",
"ThereAreNoLoginSchemesConfiguredForThisClient": "Er zijn geen aanmeldingsschema's geconfigureerd voor deze client.",
"LogInUsingYourProviderAccount": "Log in met uw {0} -account",
"DisplayName:CurrentPassword": "Huidig wachtwoord",
"DisplayName:NewPassword": "Nieuw wachtwoord",
"DisplayName:NewPasswordConfirm": "Bevestig nieuw wachtwoord",
"PasswordChangedMessage": "Uw wachtwoord is met succes veranderd.",
"DisplayName:UserName": "Gebruikersnaam",
"DisplayName:Email": "E-mail",
"DisplayName:Name": "Naam",
"DisplayName:Surname": "Achternaam",
"DisplayName:Password": "Wachtwoord",
"DisplayName:EmailAddress": "E-mail adres",
"DisplayName:PhoneNumber": "Telefoonnummer",
"PersonalSettings": "Persoonlijke instellingen",
"PersonalSettingsSaved": "Persoonlijke instellingen opgeslagen",
"PasswordChanged": "wachtwoord veranderd",
"NewPasswordConfirmFailed": "Bevestig het nieuwe wachtwoord a.u.b..",
"Manage": "Beheer",
"ManageYourProfile": "Beheer uw profiel",
"DisplayName:Abp.Account.IsSelfRegistrationEnabled": "Is zelfregistratie ingeschakeld",
"Description:Abp.Account.IsSelfRegistrationEnabled": "Of een gebruiker het account zelf kan registreren.",
"DisplayName:Abp.Account.EnableLocalLogin": "Verifieer met een lokaal account",
"Description:Abp.Account.EnableLocalLogin": "Geeft aan of de server gebruikers toestaat zich te verifiëren met een lokaal account."
}
}

2
modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/AccountController.cs

@ -18,7 +18,7 @@ namespace Volo.Abp.Account.Web.Areas.Account.Controllers
[RemoteService(Name = AccountRemoteServiceConsts.RemoteServiceName)]
[Controller]
[ControllerName("Login")]
[Area("Account")]
[Area("account")]
[Route("api/account")]
public class AccountController : AbpController
{

14
modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/de.json

@ -0,0 +1,14 @@
{
"culture": "de",
"texts": {
"Permission:Blogging": "Blog",
"Permission:Blogs": "Blogs",
"Permission:Posts": "Beiträge",
"Permission:Tags": "Tags",
"Permission:Comments": "Kommentare",
"Permission:Management": "Verwaltung",
"Permission:Edit": "Bearbeiten",
"Permission:Create": "Erstellen",
"Permission:Delete": "Löschen"
}
}

14
modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/nl.json

@ -0,0 +1,14 @@
{
"culture": "nl",
"texts": {
"Permission:Blogging": "Blog",
"Permission:Blogs": "Blogs",
"Permission:Posts": "Posts",
"Permission:Tags": "Tags",
"Permission:Comments": "Kommentaar",
"Permission:Management": "Beheer",
"Permission:Edit": "Bewerk",
"Permission:Create": "Maak aan",
"Permission:Delete": "Verwijder"
}
}

49
modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/de.json

@ -0,0 +1,49 @@
{
"culture": "de",
"texts": {
"Menu:Blogs": "Blogs",
"Menu:BlogManagement": "Blog-Verwaltung",
"Title": "Titel",
"Delete": "Löschen",
"Reply": "Antwort",
"ReplyTo": "Antwort auf {0}",
"ContinueReading": "Weiterlesen",
"DaysAgo": "vor {0} Tagen",
"YearsAgo": "vor {0} Jahren",
"MonthsAgo": "vor {0} Monaten",
"WeeksAgo": "vor {0} Wochen",
"MinutesAgo": "vor {0} Minuten",
"SecondsAgo": "vor {0} Sekunden",
"HoursAgo": "vor {0} Stunden",
"Now": "jetzt",
"Content": "Inhalt",
"SeeAll": "Alle anzeigen",
"PopularTags": "Beliebte Tags",
"WiewsWithCount": "{0} Aufrufe",
"LastPosts": "Letzte Beiträge",
"LeaveComment": "Kommentar hinterlassen",
"TagsInThisArticle": "Tags in diesem Artikel",
"Posts": "Beiträge",
"Edit": "Bearbeiten",
"BLOG": "BLOG",
"CommentDeletionWarningMessage": "Kommentar wird gelöscht.",
"PostDeletionWarningMessage": "Beitrag wird gelöscht.",
"BlogDeletionWarningMessage": "Blog wird gelöscht.",
"AreYouSure": "Sind Sie sicher?",
"CommentWithCount": "{0} Kommentare",
"Comment": "Kommentar",
"ShareOnTwitter": "Auf Twitter teilen",
"CoverImage": "Titelbild",
"CreateANewPost": "Neuen Beitrag erstellen",
"CreateANewBlog": "Neuen Blog erstellen",
"WhatIsNew": "Was ist neu?",
"Name": "Name",
"ShortName": "Kurzname",
"CreationTime": "Erstellungszeit",
"Description": "Beschreibung",
"Blogs": "Blogs",
"Tags": "Tags",
"ShareOn": "Teilen auf",
"TitleLengthWarning": "Halten Sie Ihren Titel unter 60 Zeichen, um SEO-freundlich zu sein!"
}
}

49
modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/nl.json

@ -0,0 +1,49 @@
{
"culture": "nl",
"texts": {
"Menu:Blogs": "Blogs",
"Menu:BlogManagement": "Blog Beheer",
"Title": "Titel",
"Delete": "Verwijder",
"Reply": "Antwoord",
"ReplyTo": "Antwoord aan {0}",
"ContinueReading": "Lees verder",
"DaysAgo": "{0} dagen geleden",
"YearsAgo": "{0} jaar geleden",
"MonthsAgo": "{0} maanden geleden",
"WeeksAgo": "{0} weken geleden",
"MinutesAgo": "{0} minuten geleden",
"SecondsAgo": "{0} seconden geleden",
"HoursAgo": "{0} uur geleden",
"Now": "nu",
"Content": "Inhoud",
"SeeAll": "Alles zien",
"PopularTags": "Populaire tags",
"WiewsWithCount": "{0} keer bekeken",
"LastPosts": "Laatste berichten",
"LeaveComment": "Laat commentaar achter",
"TagsInThisArticle": "Tags in dit artikel",
"Posts": "Berichten",
"Edit": "Bewerk",
"BLOG": "BLOG",
"CommentDeletionWarningMessage": "Reactie wordt verwijderd.",
"PostDeletionWarningMessage": "Berich wordt verwijderd.",
"BlogDeletionWarningMessage": "Blog wordt verwijderd.",
"AreYouSure": "Weet u het zeker?",
"CommentWithCount": "{0} reacties",
"Comment": "Reactie",
"ShareOnTwitter": "Delen op Twitter",
"CoverImage": "Omslagfoto",
"CreateANewPost": "Maak een nieuw bericht",
"CreateANewBlog": "Maak een nieuwe Blog",
"WhatIsNew": "Wat is nieuw?",
"Name": "Naam",
"ShortName": "Korte naam",
"CreationTime": "Creatie tijd",
"Description": "Beschrijving",
"Blogs": "Blogs",
"Tags": "Tags",
"ShareOn": "Delen op",
"TitleLengthWarning": "Houd uw titel kleiner dan 60 tekens om SEO-vriendelijk te zijn!"
}
}

12
modules/blogging/src/Volo.Blogging.Web/BloggingTwitterOptions.cs

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Volo.Blogging
{
public class BloggingTwitterOptions
{
public string Site { get; set; }
}
}

3
modules/blogging/src/Volo.Blogging.Web/BloggingUrlOptions.cs

@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Volo.Blogging
{

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

@ -2,16 +2,25 @@
@inherits Volo.Blogging.Pages.Blog.BloggingPage
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Http.Extensions
@using Microsoft.Extensions.Options
@using Volo.Abp.Users
@using Volo.Blogging
@using Volo.Blogging.Pages.Blog.Posts
@using Volo.Blogging.Areas.Blog.Helpers.TagHelpers
@using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs
@inject IAuthorizationService Authorization
@inject IOptionsSnapshot<BloggingTwitterOptions> twitterOptions
@model DetailModel
@{
ViewBag.Title = Model.Post.Title;
ViewBag.Description = Model.Post.Description;
ViewBag.TwitterCard = "summary_large_image";
ViewBag.TwitterSite = string.IsNullOrWhiteSpace(twitterOptions.Value.Site) ? "" : twitterOptions.Value.Site;
ViewBag.TwitterTitle = Model.Post.Title;
ViewBag.TwitterDescription = Model.Post.Description;
ViewBag.TwitterImage = $"{Request.Scheme}://{Request.Host}{Request.PathBase}{Model.Post.CoverImage}";
var hasCommentingPermission = CurrentUser.IsAuthenticated; //TODO: Apply real policy!
}
@section scripts {

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

@ -105,19 +105,23 @@
<h3 class="font-125">
<a asp-page="./Detail" asp-route-postUrl="@post.Url" asp-route-blogShortName="@Model.BlogShortName">@post.Title</a>
</h3>
<div class="article-owner">
<div class="article-infos">
<div class="user-card">
@if (post.Writer != null)
{
<h5 class="mt-2 mb-1">@post.Writer.UserName <span>@ConvertDatetimeToTimeAgo(post.CreationTime)</span></h5>
}
<i class="fa fa-eye"></i> @L["WiewsWithCount", post.ReadCount]
<span class="vs-seperator">|</span>
<i class="fa fa-comment"></i> @L["CommentWithCount", post.CommentCount]
@if (post.Writer != null)
{
<div class="article-owner">
<div class="article-infos">
<div class="user-card">
@if (post.Writer != null)
{
<h5 class="mt-2 mb-1">@post.Writer.UserName <span>@ConvertDatetimeToTimeAgo(post.CreationTime)</span></h5>
}
<i class="fa fa-eye"></i> @L["WiewsWithCount", post.ReadCount]
<span class="vs-seperator">|</span>
<i class="fa fa-comment"></i> @L["CommentWithCount", post.CommentCount]
</div>
</div>
</div>
</div>
}
</div>
</div>
</section>
@ -161,24 +165,26 @@
</p>
<a asp-page="./Detail" asp-route-postUrl="@post.Url" asp-route-blogShortName="@Model.BlogShortName" class="read-more-btn">Continue Reading &#8594;</a>
<div class="article-owner">
<div class="article-infos">
<div class="user-card">
<div class="row">
<div class="col-auto pr-1">
<img gravatar-email="@post.Writer.Email" default-image="Identicon" class="article-avatar" />
</div>
<div class="col pl-1">
<h5 class="mt-2 mb-1">@post.Writer.UserName <span>@ConvertDatetimeToTimeAgo(post.CreationTime)</span></h5>
<i class="fa fa-eye"></i> @L["WiewsWithCount", post.ReadCount]
<span class="vs-seperator">|</span>
<i class="fa fa-comment"></i> @L["CommentWithCount", post.CommentCount]
@if (post.Writer != null)
{
<div class="article-owner">
<div class="article-infos">
<div class="user-card">
<div class="row">
<div class="col-auto pr-1">
<img gravatar-email="@post.Writer.Email" default-image="Identicon" class="article-avatar" />
</div>
<div class="col pl-1">
<h5 class="mt-2 mb-1">@post.Writer.UserName <span>@ConvertDatetimeToTimeAgo(post.CreationTime)</span></h5>
<i class="fa fa-eye"></i> @L["WiewsWithCount", post.ReadCount]
<span class="vs-seperator">|</span>
<i class="fa fa-comment"></i> @L["CommentWithCount", post.CommentCount]
</div>
</div>
</div>
</div>
</div>
</div>
}
<p class="tags">
@foreach (var tag in post.Tags)
{
@ -202,9 +208,9 @@
</div>
<div class="tags">
@foreach (var popularTag in Model.PopularTags)
{
<a class="tag" asp-page="/Blogs/Posts/Index" asp-route-blogShortName="@Model.BlogShortName" asp-route-tagName="@popularTag.Name">@popularTag.Name <small>(@popularTag.UsageCount)</small></a>
}
{
<a class="tag" asp-page="/Blogs/Posts/Index" asp-route-blogShortName="@Model.BlogShortName" asp-route-tagName="@popularTag.Name">@popularTag.Name <small>(@popularTag.UsageCount)</small></a>
}
</div>
</div>
}

10
modules/docs/app/VoloDocs.Web/Localization/Resources/VoloDocs/Web/de.json

@ -0,0 +1,10 @@
{
"culture": "de",
"texts": {
"DocsTitle": "VoloDocs",
"WelcomeVoloDocs": "Willkommen bei den VoloDocs!",
"NoProjectWarning": "Es gibt noch kein definiertes Projekt!",
"CreateYourFirstProject": "Klicken Sie hier, um Ihr erstes Projekt zu starten",
"NoProject": "Kein Projekt!"
}
}

10
modules/docs/app/VoloDocs.Web/Localization/Resources/VoloDocs/Web/nl.json

@ -0,0 +1,10 @@
{
"culture": "nl",
"texts": {
"DocsTitle": "VoloDocs",
"WelcomeVoloDocs": "Welkom bij de VoloDocs!",
"NoProjectWarning": "Er is nog geen gedefinieerd project!",
"CreateYourFirstProject": "Klik hier om uw eerste project te starten",
"NoProject": "Geen project!"
}
}

7
modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Documents/IDocumentAdminAppService.cs

@ -1,4 +1,5 @@
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
namespace Volo.Docs.Admin.Documents
@ -10,7 +11,5 @@ namespace Volo.Docs.Admin.Documents
Task PullAllAsync(PullAllDocumentInput input);
Task PullAsync(PullDocumentInput input);
Task ReindexAsync();
}
}
}

37
modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/de.json

@ -0,0 +1,37 @@
{
"culture": "de",
"texts": {
"Permission:DocumentManagement": "Dokumentenverwaltung",
"Permission:Projects": "Projekte",
"Permission:Edit": "Bearbeiten",
"Permission:Delete": "Löschen",
"Permission:Create": "Erstellen",
"Permission:Documents": "Dokumente",
"Menu:DocumentManagement": "Dokumente",
"Menu:ProjectManagement": "Projekte",
"CreateANewProject": "Neues Projekt erstellen",
"Edit": "Bearbeiten",
"Create": "Erstellen",
"Pull": "Pull",
"Projects": "Projekte",
"Name": "Name",
"ShortName": "Kurzname",
"DocumentStoreType": "DocumentStoreType",
"Format": "Format",
"ShortNameInfoText": "Wird für eindeutige URL verwendet.",
"DisplayName:Name": "Name",
"DisplayName:ShortName": "Kurzname",
"DisplayName:Format": "Format",
"DisplayName:DefaultDocumentName": "Standard-Dokumentname",
"DisplayName:NavigationDocumentName": "Name des Navigationsdokuments",
"DisplayName:MinimumVersion": "Mindestversion",
"DisplayName:MainWebsiteUrl": "Haupt-URL der Website",
"DisplayName:LatestVersionBranchName": "Zweigname der neuesten Version",
"DisplayName:GitHubRootUrl": "GitHub-Stamm-URL",
"DisplayName:GitHubAccessToken": "GitHub-Zugriffstoken",
"DisplayName:GitHubUserAgent": "GitHub-Benutzer-Agent",
"DisplayName:All": "Pull all",
"DisplayName:LanguageCode": "Sprachcode",
"DisplayName:Version": "Version"
}
}

37
modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/nl.json

@ -0,0 +1,37 @@
{
"culture": "nl",
"texts": {
"Permission:DocumentManagement": "Document beheer",
"Permission:Projects": "Projecten",
"Permission:Edit": "Bewerk",
"Permission:Delete": "Verwijder",
"Permission:Create": "Maak aan",
"Permission:Documents": "Documenten",
"Menu:DocumentManagement": "Documenten",
"Menu:ProjectManagement": "Projecten",
"CreateANewProject": "Maak een nieuw project",
"Edit": "Bewerk",
"Create": "Maak aan",
"Pull": "Pull",
"Projects": "Projecten",
"Name": "Naam",
"ShortName": "Korte naam",
"DocumentStoreType": "DocumentStoreType",
"Format": "Formaat",
"ShortNameInfoText": "Wordt gebruikt voor unieke URL.",
"DisplayName:Name": "Naam",
"DisplayName:ShortName": "Korte naam",
"DisplayName:Format": "Formaat",
"DisplayName:DefaultDocumentName": "Standaard documentnaam",
"DisplayName:NavigationDocumentName": "Navigatiedocumentnaam",
"DisplayName:MinimumVersion": "Minimale versie",
"DisplayName:MainWebsiteUrl": "URL van hoofdwebsite",
"DisplayName:LatestVersionBranchName": "Laatste versie vertakkingsnaam",
"DisplayName:GitHubRootUrl": "GitHub root URL",
"DisplayName:GitHubAccessToken": "GitHub-toegangstoken",
"DisplayName:GitHubUserAgent": "GitHub-gebruikersagent",
"DisplayName:All": "Pull all",
"DisplayName:LanguageCode": "Taalcode",
"DisplayName:Version": "Versie"
}
}

10
modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Projects/IProjectAdminAppService.cs

@ -12,9 +12,13 @@ namespace Volo.Docs.Admin.Projects
Task<ProjectDto> GetAsync(Guid id);
Task<ProjectDto> CreateAsync(CreateProjectDto input);
Task<ProjectDto> UpdateAsync(Guid id, UpdateProjectDto input);
Task DeleteAsync(Guid id);
Task ReindexAsync(ReindexInput input);
Task ReindexAllAsync();
}
}
}

9
modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Projects/ReindexInput.cs

@ -0,0 +1,9 @@
using System;
namespace Volo.Docs.Admin.Projects
{
public class ReindexInput
{
public Guid ProjectId { get; set; }
}
}

36
modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Documents/DocumentAdminAppService.cs

@ -25,17 +25,13 @@ namespace Volo.Docs.Admin.Documents
private readonly IDistributedCache<DocumentUpdateInfo> _documentUpdateCache;
private readonly IDistributedCache<List<VersionInfo>> _versionCache;
private readonly IDistributedCache<LanguageConfig> _languageCache;
private readonly IDistributedCache<DocumentResource> _resourceCache;
private readonly IDocumentFullSearch _documentFullSearch;
public DocumentAdminAppService(IProjectRepository projectRepository,
IDocumentRepository documentRepository,
IDocumentSourceFactory documentStoreFactory,
IDistributedCache<DocumentUpdateInfo> documentUpdateCache,
IDistributedCache<List<VersionInfo>> versionCache,
IDistributedCache<LanguageConfig> languageCache,
IDistributedCache<DocumentResource> resourceCache,
IDocumentFullSearch documentFullSearch)
IDistributedCache<LanguageConfig> languageCache)
{
_projectRepository = projectRepository;
_documentRepository = documentRepository;
@ -43,8 +39,6 @@ namespace Volo.Docs.Admin.Documents
_documentUpdateCache = documentUpdateCache;
_versionCache = versionCache;
_languageCache = languageCache;
_resourceCache = resourceCache;
_documentFullSearch = documentFullSearch;
LocalizationResource = typeof(DocsResource);
}
@ -125,32 +119,6 @@ namespace Volo.Docs.Admin.Documents
await UpdateDocumentUpdateInfoCache(sourceDocument);
}
public async Task ReindexAsync()
{
var docs = await _documentRepository.GetListAsync();
var projects = await _projectRepository.GetListAsync();
foreach (var doc in docs)
{
var project = projects.FirstOrDefault(x => x.Id == doc.ProjectId);
if (project == null)
{
continue;
}
if (doc.FileName == project.NavigationDocumentName)
{
continue;
}
if (doc.FileName == project.ParametersDocumentName)
{
continue;
}
await _documentFullSearch.AddOrUpdateAsync(doc);
}
}
private async Task UpdateDocumentUpdateInfoCache(Document document)
{
var cacheKey = $"DocumentUpdateInfo{document.ProjectId}#{document.Name}#{document.LanguageCode}#{document.Version}";
@ -174,4 +142,4 @@ namespace Volo.Docs.Admin.Documents
return document;
}
}
}
}

71
modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Projects/ProjectAdminAppService.cs

@ -1,10 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Guids;
using Volo.Docs.Documents;
using Volo.Docs.Documents.FullSearch.Elastic;
using Volo.Docs.Localization;
using Volo.Docs.Projects;
@ -14,15 +17,22 @@ namespace Volo.Docs.Admin.Projects
public class ProjectAdminAppService : ApplicationService, IProjectAdminAppService
{
private readonly IProjectRepository _projectRepository;
private readonly IDocumentRepository _documentRepository;
private readonly IDocumentFullSearch _documentFullSearch;
private readonly IGuidGenerator _guidGenerator;
public ProjectAdminAppService(
IProjectRepository projectRepository, IGuidGenerator guidGenerator)
IProjectRepository projectRepository,
IDocumentRepository documentRepository,
IDocumentFullSearch documentFullSearch,
IGuidGenerator guidGenerator)
{
ObjectMapperContext = typeof(DocsAdminApplicationModule);
LocalizationResource = typeof(DocsResource);
_projectRepository = projectRepository;
_documentRepository = documentRepository;
_documentFullSearch = documentFullSearch;
_guidGenerator = guidGenerator;
}
@ -51,7 +61,7 @@ namespace Volo.Docs.Admin.Projects
{
throw new ProjectShortNameAlreadyExistsException(input.ShortName);
}
var project = new Project(_guidGenerator.Create(),
input.Name,
input.ShortName,
@ -71,7 +81,7 @@ namespace Volo.Docs.Admin.Projects
{
project.ExtraProperties.Add(extraProperty.Key, extraProperty.Value);
}
project = await _projectRepository.InsertAsync(project);
return ObjectMapper.Map<Project, ProjectDto>(project);
@ -106,6 +116,57 @@ namespace Volo.Docs.Admin.Projects
{
await _projectRepository.DeleteAsync(id);
}
public async Task ReindexAsync(ReindexInput input)
{
var project = await _projectRepository.GetAsync(input.ProjectId);
await _documentFullSearch.DeleteAllByProjectIdAsync(project.Id);
var docs = await _documentRepository.GetListByProjectId(project.Id);
foreach (var doc in docs)
{
if (doc.FileName == project.NavigationDocumentName)
{
continue;
}
if (doc.FileName == project.ParametersDocumentName)
{
continue;
}
await _documentFullSearch.AddOrUpdateAsync(doc);
}
}
public async Task ReindexAllAsync()
{
await _documentFullSearch.DeleteAllAsync();
var docs = await _documentRepository.GetListAsync();
var projects = await _projectRepository.GetListAsync();
foreach (var doc in docs)
{
var project = projects.FirstOrDefault(x => x.Id == doc.ProjectId);
if (project == null)
{
continue;
}
if (doc.FileName == project.NavigationDocumentName)
{
continue;
}
if (doc.FileName == project.ParametersDocumentName)
{
continue;
}
await _documentFullSearch.AddOrUpdateAsync(doc);
}
}
}
}
}

7
modules/docs/src/Volo.Docs.Admin.HttpApi/Volo/Docs/Admin/DocumentsAdminController.cs

@ -39,12 +39,5 @@ namespace Volo.Docs.Admin
{
return _documentAdminAppService.PullAsync(input);
}
[HttpPost]
[Route("Reindex")]
public Task ReindexAsync()
{
return _documentAdminAppService.ReindexAsync();
}
}
}

14
modules/docs/src/Volo.Docs.Admin.HttpApi/Volo/Docs/Admin/ProjectsAdminController.cs

@ -53,5 +53,19 @@ namespace Volo.Docs.Admin
{
return _projectAppService.DeleteAsync(id);
}
[HttpPost]
[Route("ReindexAll")]
public Task ReindexAllAsync()
{
return _projectAppService.ReindexAllAsync();
}
[HttpPost]
[Route("Reindex")]
public Task ReindexAsync(ReindexInput input)
{
return _projectAppService.ReindexAsync(input);
}
}
}

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

@ -39,6 +39,10 @@
</abp-dropdown-menu>
</abp-dropdown>
}
@if (await Authorization.IsGrantedAsync(DocsAdminPermissions.Projects.Default))
{
<abp-button button-type="Primary" icon="plus" text="@L["ReIndexAllProjects"].Value" id="ReIndexAllProjects" />
}
</abp-column>
</abp-row>
</abp-card-header>

27
modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Projects/index.js

@ -74,6 +74,18 @@
_dataTable.ajax.reload();
});
}
},
{
text: l('ReIndexProject'),
visible: abp.auth.isGranted('Docs.Admin.Documents'),
confirmMessage: function (data) { return l('ReIndexProjectConfirmationMessage', data.record.name); },
action: function (data) {
volo.docs.admin.projectsAdmin
.reindex({ projectId: data.record.id})
.then(function () {
abp.message.success(l('SuccessfullyReIndexProject', data.record.name));
});
}
}
]
}
@ -110,6 +122,19 @@
_createModal.open({source:"GitHub"});
});
$("#ReIndexAllProjects").click(function (event) {
abp.message.confirm(l('ReIndexAllProjectConfirmationMessage'))
.done(function (accepted) {
if (accepted) {
volo.docs.admin.projectsAdmin
.reindexAll()
.then(function () {
abp.message.success(l('SuccessfullyReIndexAllProject'));
});
}
});
});
_createModal.onClose(function () {
_dataTable.ajax.reload();
});
@ -118,4 +143,4 @@
_dataTable.ajax.reload();
});
});
});

44
modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/ElasticDocumentFullSearch.cs

@ -92,6 +92,48 @@ namespace Volo.Docs.Documents.FullSearch.Elastic
.DeleteAsync(DocumentPath<Document>.Id(id), x => x.Index(_options.IndexName), cancellationToken));
}
public async Task DeleteAllAsync(CancellationToken cancellationToken = default)
{
ValidateElasticSearchEnabled();
var request = new DeleteByQueryRequest(_options.IndexName)
{
Query = new MatchAllQuery()
};
HandleError(await _clientProvider.GetClient()
.DeleteByQueryAsync(request, cancellationToken));
}
public async Task DeleteAllByProjectIdAsync(Guid projectId, CancellationToken cancellationToken = default)
{
ValidateElasticSearchEnabled();
var request = new DeleteByQueryRequest(_options.IndexName)
{
Query = new BoolQuery
{
Filter = new QueryContainer[]
{
new BoolQuery
{
Must = new QueryContainer[]
{
new TermQuery
{
Field = "projectId",
Value = projectId
}
}
}
}
},
};
HandleError(await _clientProvider.GetClient()
.DeleteByQueryAsync(request, cancellationToken));
}
public async Task<List<EsDocument>> SearchAsync(string context, Guid projectId, string languageCode,
string version, int? skipCount = null, int? maxResultCount = null,
CancellationToken cancellationToken = default)
@ -185,4 +227,4 @@ namespace Volo.Docs.Documents.FullSearch.Elastic
}
}
}
}
}

6
modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/FullSearch/Elastic/IDocumentFullSearch.cs

@ -13,8 +13,12 @@ namespace Volo.Docs.Documents.FullSearch.Elastic
Task DeleteAsync(Guid id, CancellationToken cancellationToken = default);
Task DeleteAllAsync(CancellationToken cancellationToken = default);
Task DeleteAllByProjectIdAsync(Guid projectId, CancellationToken cancellationToken = default);
Task<List<EsDocument>> SearchAsync(string context, Guid projectId, string languageCode,
string version, int? skipCount = null, int? maxResultCount = null,
CancellationToken cancellationToken = default);
}
}
}

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

Loading…
Cancel
Save