Browse Source

Merge branch 'dev' into maliming/IdentityServer-v4

pull/4578/head
maliming 6 years ago
parent
commit
fa1cec325c
  1. 4
      common.props
  2. 5
      docs/en/Background-Jobs-Hangfire.md
  3. 8
      docs/en/CLI.md
  4. 18
      docs/en/Getting-Started.md
  5. 20
      docs/en/Index.md
  6. 2
      docs/en/Samples/Index.md
  7. 2
      docs/en/Samples/Microservice-Demo.md
  8. 6
      docs/en/SignalR-Integration.md
  9. 912
      docs/en/Tutorials/Part-1.md
  10. 1307
      docs/en/Tutorials/Part-2.md
  11. 1227
      docs/en/Tutorials/Part-3.md
  12. 250
      docs/en/Tutorials/Part-4.md
  13. 401
      docs/en/Tutorials/Part-5.md
  14. BIN
      docs/en/Tutorials/images/bookstore-book-and-booktype.png
  15. BIN
      docs/en/Tutorials/images/bookstore-book-list-3.png
  16. BIN
      docs/en/Tutorials/images/bookstore-book-list.png
  17. BIN
      docs/en/Tutorials/images/bookstore-dbmigrator-on-solution.png
  18. BIN
      docs/en/Tutorials/images/bookstore-edit-button-2.png
  19. BIN
      docs/en/Tutorials/images/bookstore-edit-delete-actions.png
  20. BIN
      docs/en/Tutorials/images/bookstore-getlist-result-network.png
  21. BIN
      docs/en/Tutorials/images/bookstore-index-js-file-v3.png
  22. BIN
      docs/en/Tutorials/images/bookstore-javascript-proxy-console.png
  23. BIN
      docs/en/Tutorials/images/bookstore-new-book-button-2.png
  24. BIN
      docs/en/Tutorials/images/bookstore-new-book-button-small.png
  25. BIN
      docs/en/Tutorials/images/bookstore-permissions-ui.png
  26. BIN
      docs/en/Tutorials/images/generated-proxies-2.png
  27. 38
      docs/en/UI/Angular/Component-Replacement.md
  28. 71
      docs/en/UI/Angular/Config-State.md
  29. 73
      docs/en/UI/Angular/Migration-Guide-v3.md
  30. 20
      docs/en/docs-nav.json
  31. 8
      docs/pt-BR/Tutorials/Angular/Part-II.md
  32. 5
      docs/zh-Hans/Background-Jobs-Hangfire.md
  33. 8
      docs/zh-Hans/Local-Event-Bus.md
  34. 4
      docs/zh-Hans/Repositories.md
  35. 12
      docs/zh-Hans/Tutorials/Part-2.md
  36. 36
      docs/zh-Hans/UI/Angular/Component-Replacement.md
  37. 1
      docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Form-elements.md
  38. 2
      docs/zh-Hans/docs-nav.json
  39. 27
      framework/src/Volo.Abp.AspNetCore.Authentication.OAuth/Properties/launchSettings.json
  40. 27
      framework/src/Volo.Abp.AspNetCore.MultiTenancy/Properties/launchSettings.json
  41. 27
      framework/src/Volo.Abp.AspNetCore.Mvc.Client/Properties/launchSettings.json
  42. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/nl.json
  43. 31
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/layout.css
  44. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/AbpAspNetCoreMvcUiThemeSharedDemoModule.cs
  45. 10
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.csproj
  46. 27
      framework/src/Volo.Abp.AspNetCore.Serilog/Properties/launchSettings.json
  47. 27
      framework/src/Volo.Abp.AspNetCore.SignalR/Properties/launchSettings.json
  48. 27
      framework/src/Volo.Abp.AspNetCore.TestBase/Properties/launchSettings.json
  49. 27
      framework/src/Volo.Abp.AspNetCore/Properties/launchSettings.json
  50. 4
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/AbpExceptionHandlingMiddleware.cs
  51. 15
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GenerateProxyCommand.cs
  52. 14
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs
  53. 3
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/SolutionRenameStep.cs
  54. 6
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AngularEnvironmentFilePortChangeForSeparatedIdentityServersStep.cs
  55. 72
      framework/src/Volo.Abp.Core/System/AbpStringExtensions.cs
  56. 2
      framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Localization/Resources/AbpDdd/de.json
  57. 12
      framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/de.json
  58. 27
      framework/src/Volo.Abp.Http.Client.IdentityModel.Web/Properties/launchSettings.json
  59. 2
      framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TZConvertTimezoneProvider.cs
  60. 2
      framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json
  61. 38
      framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/de.json
  62. 46
      framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/de.json
  63. 2
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/de.json
  64. 6
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/Pages/Components/Dropdowns/Index.cshtml
  65. 12
      framework/test/Volo.Abp.Core.Tests/System/StringExtensions_Tests.cs
  66. 5
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalization_Tests.cs
  67. 2
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/nl.json
  68. 2
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/nl.json
  69. 2
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/nl.json
  70. 2
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/nl.json
  71. 5
      modules/account/.prettierrc
  72. 5
      modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/cs.json
  73. 26
      modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/de.json
  74. 6
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/LoggedOut.cshtml
  75. 12
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/LoggedOut.js
  76. 39
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.js
  77. 5
      modules/audit-logging/.prettierrc
  78. 4
      modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditingStore.cs
  79. 5
      modules/background-jobs/.prettierrc
  80. 5
      modules/blob-storing-database/.prettierrc
  81. 5
      modules/blogging/.prettierrc
  82. 10
      modules/blogging/Volo.Blogging.sln
  83. 12
      modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo.Blogging.Admin.Application.Contracts.csproj
  84. 14
      modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/BloggingAdminApplicationContractsModule.cs
  85. 25
      modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/BloggingAdminPermissionDefinitionProvider.cs
  86. 23
      modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/BloggingAdminPermissions.cs
  87. 11
      modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/cs.json
  88. 11
      modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/de.json
  89. 11
      modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/en.json
  90. 11
      modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/nl.json
  91. 11
      modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/pl-PL.json
  92. 11
      modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/pt-BR.json
  93. 11
      modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/sl.json
  94. 11
      modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/tr.json
  95. 11
      modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/vi.json
  96. 11
      modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/zh-Hans.json
  97. 11
      modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/zh-Hant.json
  98. 6
      modules/blogging/src/Volo.Blogging.Admin.Application/Volo/Blogging/Admin/Blogs/BlogManagementAppService.cs
  99. 4
      modules/blogging/src/Volo.Blogging.Admin.Web/BloggingAdminMenuContributor.cs
  100. 2
      modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/Create.cshtml.cs

4
common.props

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

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

@ -66,9 +66,7 @@ After you have installed these NuGet packages, you need to configure your projec
}
````
2. We need to add `UseHangfireServer` call in the `OnApplicationInitialization` method in `Module` class
If you want to use hangfire's dashboard, you can add it, too: by `UseHangfireDashboard`
2. If you want to use hangfire's dashboard, you can add `UseHangfireDashboard` call in the `OnApplicationInitialization` method in `Module` class
````csharp
public override void OnApplicationInitialization(ApplicationInitializationContext context)
@ -77,7 +75,6 @@ If you want to use hangfire's dashboard, you can add it, too: by `UseHangfireDas
// ... others
app.UseHangfireServer();
app.UseHangfireDashboard();
}

8
docs/en/CLI.md

@ -82,7 +82,7 @@ abp new Acme.BookStore
* `--mobile` or `-m`: Specifies the mobile application framework. Default framework is `react-native`. Available frameworks:
* `none`: no mobile application.
* `react-native`: React Native.
* `--database-provider` or `-d`: Specifies the database provider. Default provider is `ef`. Available providers:
* `--database-provider` or `-d`: Specifies the database provider. Default provider is `ef`. Available providers:
* `ef`: Entity Framework Core.
* `mongodb`: MongoDB.
* **`module`**: [Module template](Startup-Templates/Module.md). Additional options:
@ -90,10 +90,10 @@ abp new Acme.BookStore
* **`console`**: [Console template](Startup-Templates/Console.md).
* `--output-folder` or `-o`: Specifies the output folder. Default value is the current directory.
* `--version` or `-v`: Specifies the ABP & template version. It can be a [release tag](https://github.com/abpframework/abp/releases) or a [branch name](https://github.com/abpframework/abp/branches). Uses the latest release if not specified. Most of the times, you will want to use the latest version.
* `--template-source` or `-ts`: Specifies a custom template source to use to build the project. Local and network sources can be used(Like `D\localTemplate` or `https://<your url>.zip`).
* `--template-source` or `-ts`: Specifies a custom template source to use to build the project. Local and network sources can be used(Like `D:\local-template` or `https://.../my-template-file.zip`).
* `--create-solution-folder` or `-csf`: Specifies if the project will be in a new folder in the output folder or directly the output folder.
* `--connection-string` or `-cs`: Overwrites the default connection strings in all `appsettings.json` files. The default connection string is `Server=localhost;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true`. You can set your own connection string if you don't want to use the default. Be aware that the default database provider is `SQL Server`, therefore you can only enter connection string for SQL Server!
* `--local-framework-ref --abp-path`: keeps local references to projects instead of replacing with NuGet package references.
* `--connection-string` or `-cs`: Overwrites the default connection strings in all `appsettings.json` files. The default connection string is `Server=localhost;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true` for EF Core and it is configured to use the SQL Server. If you want to use the EF Core, but need to change the DBMS, you can change it as [described here](Entity-Framework-Core-Other-DBMS.md) (after creating the solution).
* `--local-framework-ref --abp-path`: Uses local projects references to the ABP framework instead of using the NuGet packages. This can be useful if you download the ABP Framework source code and have a local reference to the framework from your application.
### update

18
docs/en/Getting-Started.md

@ -20,19 +20,20 @@ First things first! Let's setup your development environment before creating the
The following tools should be installed on your development machine:
* [Visual Studio 2019 (v16.4+)](https://visualstudio.microsoft.com/vs/) for Windows / [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/).*
* [Visual Studio 2019 (v16.4+)](https://visualstudio.microsoft.com/vs/) for Windows / [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/). <sup id="a-editor">[1](#f-editor)</sup>
* [.NET Core 3.1+](https://www.microsoft.com/net/download/dotnet-core/)
* [Node v12+](https://nodejs.org)
* [Yarn v1.19+](https://classic.yarnpkg.com/)
* [Node v12 or v14](https://nodejs.org/en/)
* [Yarn v1.20+ (not v2)](https://classic.yarnpkg.com/en/docs/install) <sup id="a-yarn">[2](#f-yarn)</sup> or npm v6+ (installed with Node)
{{ if Tiered == "Yes" }}
* [Redis](https://redis.io/) (the startup solution uses the Redis as the [distributed cache](Caching.md)).
{{ end }}
<sup id="f-editor"><b>1</b></sup> _You can use another editor instead of Visual Studio as long as it supports .NET Core and ASP.NET Core._ <sup>[↩](#a-editor)</sup>
> *You can use another editor instead of Visual Studio as long as it supports .NET Core and ASP.NET Core.
<sup id="f-yarn"><b>2</b></sup> _Yarn v2 works differently and is not supported._ <sup>[↩](#a-yarn)</sup>
### Install the ABP CLI
@ -64,6 +65,8 @@ Use the `new` command of the ABP CLI to create a new project:
abp new Acme.BookStore{{if UI == "NG"}} -u angular {{end}}{{if DB == "Mongo"}} -d mongodb{{end}}{{if Tiered == "Yes" && UI != "NG"}} --tiered {{else if Tiered == "Yes" && UI == "NG"}}--separate-identity-server{{end}}
````
> This command also creates a React Native mobile application inside the solution folder. If you don't want it, you can safely delete it or specify the `-m none` option to the `abp new` command to not include it in the solution at all.
{{ if UI == "NG" }}
* `-u` argument specifies the UI framework, `angular` in this case.
@ -322,7 +325,6 @@ Once all node modules are loaded, execute `yarn start` (or `npm start`) command:
yarn start
```
Wait `Angular CLI` to launch `Webpack` dev-server with `BrowserSync`.
This will take care of compiling your `TypeScript` code, and automatically reloading your browser.
After it finishes, `Angular Live Development Server` will be listening on localhost:4200,
open your web browser and navigate to [localhost:4200](http://localhost:4200/)
@ -341,10 +343,10 @@ When you create a new application, the solution includes `react-native` folder b
If you don't plan to develop a mobile application with React Native, you can safely delete the `react-native` folder.
> You can specifying the `--mobile none` option to the ABP CLI to not create the `react-native` folder in the beginning.
> You can specifying the `-m none` option to the ABP CLI to not create the `react-native` folder in the beginning.
See the "[Getting Started with the React Native](Getting-Started-React-Native.md)" document to learn how to configure and run the React Native application.
## See Also
## Next
* [Web Application Development Tutorial](Tutorials/Part-1.md)
* [Web Application Development Tutorial](Tutorials/Part-1.md)

20
docs/en/Index.md

@ -1,28 +1,18 @@
# ABP Documentation
ABP is an **open source application framework** focused on ASP.NET Core based web application development, but also supports developing other type of applications.
ABP is an **open source application framework** focused on **ASP.NET Core** based **web application development**. It also supports developing other type of applications.
Explore the left navigation menu to deep dive in the documentation.
Explore the navigation menu to deep dive in the documentation.
## Getting Started
Easiest way to start a new project with ABP is to use the startup templates:
The easiest way to start a new web application with the ABP Framework is to use the [getting started](Getting-Started.md) tutorial.
* [ASP.NET Core MVC (Razor Pages) UI Startup Template](Getting-Started.md?UI=MVC&DB=EF&Tiered=No)
* [Angular UI Startup Template](Getting-Started.md?UI=NG&DB=EF&Tiered=No)
If you want to start from scratch (with an empty project) then manually install the ABP Framework and use the following tutorials:
* [Console Application](Getting-Started-Console-Application.md)
* [ASP.NET Core Web Application](Getting-Started-AspNetCore-Application.md)
## Packages
ABP Framework is distributed as NuGet and NPM packages. See [this page](http://abp.io/packages) for the complete list of the packages.
Then you can continue with the [web application development tutorial](Tutorials/Part-1.md).
## Source Code
ABP is hosted on GitHub. See [the source code](https://github.com/abpframework/abp).
ABP is hosted on GitHub. See [the source code](https://github.com/abpframework).
## Want to Contribute?

2
docs/en/Samples/Index.md

@ -7,7 +7,7 @@ Here, a list of official samples built with the ABP Framework. Most of these sam
A complete solution to demonstrate how to build systems based on the microservice architecture.
* [The complete documentation for this sample](Microservice-Demo.md)
* [Source code](https://github.com/abpframework/abp/tree/dev/samples/MicroserviceDemo)
* [Source code](https://github.com/abpframework/abp-samples/tree/master/MicroserviceDemo)
* [Microservice architecture document](../Microservice-Architecture.md)
### Book Store

2
docs/en/Samples/Microservice-Demo.md

@ -28,7 +28,7 @@ The diagram below shows the system:
### Source Code
You can get the source code from [the GitHub repository](https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo).
You can get the source code from [the GitHub repository](https://github.com/abpframework/abp-samples/tree/master/MicroserviceDemo).
## Running the Solution

6
docs/en/SignalR-Integration.md

@ -115,7 +115,7 @@ public class MessagingHub : Hub
}
````
The hub route will be `/signalr-hubs/messasing` for the `MessasingHub`:
The hub route will be `/signalr-hubs/messaging` for the `MessagingHub`:
* Adding a standard `/signalr-hubs/` prefix
* Continue with the **camel case** hub name, without the `Hub` suffix.
@ -123,7 +123,7 @@ The hub route will be `/signalr-hubs/messasing` for the `MessasingHub`:
If you want to specify the route, you can use the `HubRoute` attribute:
````csharp
[HubRoute("/my-messasing-hub")]
[HubRoute("/my-messaging-hub")]
public class MessagingHub : Hub
{
//...
@ -132,7 +132,7 @@ public class MessagingHub : Hub
### AbpHub Base Classes
Instead of the standard `Hub` and `Hub<T>` classes, you can inherit from the `AbpHub` or `AbpHub<T>` which hve useful base properties like `CurrentUser`.
Instead of the standard `Hub` and `Hub<T>` classes, you can inherit from the `AbpHub` or `AbpHub<T>` which have useful base properties like `CurrentUser`.
Example:

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

File diff suppressed because it is too large

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

File diff suppressed because it is too large

1227
docs/en/Tutorials/Part-3.md

File diff suppressed because it is too large

250
docs/en/Tutorials/Part-4.md

@ -0,0 +1,250 @@
# Web Application Development Tutorial - Part 4: Integration Tests
````json
//[doc-params]
{
"UI": ["MVC","NG"],
"DB": ["EF","Mongo"]
}
````
{{
if UI == "MVC"
UI_Text="mvc"
else if UI == "NG"
UI_Text="angular"
else
UI_Text="?"
end
if DB == "EF"
DB_Text="Entity Framework Core"
else if DB == "Mongo"
DB_Text="MongoDB"
else
DB_Text="?"
end
}}
## About This Tutorial
In this tutorial series, you will build an ABP based web application named `Acme.BookStore`. This application is used to manage a list of books and their authors. It is developed using the following technologies:
* **{{DB_Text}}** as the ORM provider.
* **{{UI_Value}}** as the UI Framework.
This tutorial is organized as the following parts;
- [Part 1: Creating the project and book list page](Part-1.md)
- [Part 2: The book list page](Part-2.md)
- [Part 3: Creating, updating and deleting books](Part-3.md)
- **Part 4: Integration tests (this part)**
- [Part 5: Authorization](Part-5.md)
### Download the Source Code
This tutorials has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded:
* [MVC (Razor Pages) UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore)
* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
## Test Projects in the Solution
This part covers the **server side** tests. There are several test projects in the solution:
![bookstore-test-projects-v2](./images/bookstore-test-projects-{{UI_Text}}.png)
Each project is used to test the related project. Test projects use the following libraries for testing:
* [Xunit](https://xunit.github.io/) as the main test framework.
* [Shoudly](http://shouldly.readthedocs.io/en/latest/) as the assertion library.
* [NSubstitute](http://nsubstitute.github.io/) as the mocking library.
{{if DB=="EF"}}
> The test projects are configured to use **SQLite in-memory** as the database. A separate database instance is created and seeded (with the data seed system) to prepare a fresh database for every test.
{{else if DB=="Mongo"}}
> **[Mongo2Go](https://github.com/Mongo2Go/Mongo2Go)** library is used to mock the MongoDB database. A separate database instance is created and seeded (with the data seed system) to prepare a fresh database for every test.
{{end}}
## Adding Test Data
If you had created a data seed contributor as described in the [first part](Part-1.md), the same data will be available in your tests. So, you can skip this section. If you haven't created the seed contributor, you can use the `BookStoreTestDataSeedContributor` to seed the same data to be used in the tests below.
## Testing the BookAppService
Create a test class named `BookAppService_Tests` in the `Acme.BookStore.Application.Tests` project:
````csharp
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Application.Dtos;
using Xunit;
namespace Acme.BookStore.Books
{ {{if DB=="Mongo"}}
[Collection(BookStoreTestConsts.CollectionDefinitionName)]{{end}}
public class BookAppService_Tests : BookStoreApplicationTestBase
{
private readonly IBookAppService _bookAppService;
public BookAppService_Tests()
{
_bookAppService = GetRequiredService<IBookAppService>();
}
[Fact]
public async Task Should_Get_List_Of_Books()
{
//Act
var result = await _bookAppService.GetListAsync(
new PagedAndSortedResultRequestDto()
);
//Assert
result.TotalCount.ShouldBeGreaterThan(0);
result.Items.ShouldContain(b => b.Name == "1984");
}
}
}
````
* `Should_Get_List_Of_Books` test simply uses `BookAppService.GetListAsync` method to get and check the list of books.
* We can safely check the book "1984" by its name, because we know that this books is available in the database since we've added it in the seed data.
Add a new test method to the `BookAppService_Tests` class that creates a new **valid** book:
````csharp
[Fact]
public async Task Should_Create_A_Valid_Book()
{
//Act
var result = await _bookAppService.CreateAsync(
new CreateUpdateBookDto
{
Name = "New test book 42",
Price = 10,
PublishDate = System.DateTime.Now,
Type = BookType.ScienceFiction
}
);
//Assert
result.Id.ShouldNotBe(Guid.Empty);
result.Name.ShouldBe("New test book 42");
}
````
Add a new test that tries to create an invalid book and fails:
````csharp
[Fact]
public async Task Should_Not_Create_A_Book_Without_Name()
{
var exception = await Assert.ThrowsAsync<AbpValidationException>(async () =>
{
await _bookAppService.CreateAsync(
new CreateUpdateBookDto
{
Name = "",
Price = 10,
PublishDate = DateTime.Now,
Type = BookType.ScienceFiction
}
);
});
exception.ValidationErrors
.ShouldContain(err => err.MemberNames.Any(mem => mem == "Name"));
}
````
* Since the `Name` is empty, ABP will throw an `AbpValidationException`.
The final test class should be as shown below:
````csharp
using System;
using System.Linq;
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Validation;
using Xunit;
namespace Acme.BookStore.Books
{ {{if DB=="Mongo"}}
[Collection(BookStoreTestConsts.CollectionDefinitionName)]{{end}}
public class BookAppService_Tests : BookStoreApplicationTestBase
{
private readonly IBookAppService _bookAppService;
public BookAppService_Tests()
{
_bookAppService = GetRequiredService<IBookAppService>();
}
[Fact]
public async Task Should_Get_List_Of_Books()
{
//Act
var result = await _bookAppService.GetListAsync(
new PagedAndSortedResultRequestDto()
);
//Assert
result.TotalCount.ShouldBeGreaterThan(0);
result.Items.ShouldContain(b => b.Name == "1984");
}
[Fact]
public async Task Should_Create_A_Valid_Book()
{
//Act
var result = await _bookAppService.CreateAsync(
new CreateUpdateBookDto
{
Name = "New test book 42",
Price = 10,
PublishDate = System.DateTime.Now,
Type = BookType.ScienceFiction
}
);
//Assert
result.Id.ShouldNotBe(Guid.Empty);
result.Name.ShouldBe("New test book 42");
}
[Fact]
public async Task Should_Not_Create_A_Book_Without_Name()
{
var exception = await Assert.ThrowsAsync<AbpValidationException>(async () =>
{
await _bookAppService.CreateAsync(
new CreateUpdateBookDto
{
Name = "",
Price = 10,
PublishDate = DateTime.Now,
Type = BookType.ScienceFiction
}
);
});
exception.ValidationErrors
.ShouldContain(err => err.MemberNames.Any(mem => mem == "Name"));
}
}
}
````
Open the **Test Explorer Window** (use Test -> Windows -> Test Explorer menu if it is not visible) and **Run All** tests:
![bookstore-appservice-tests](./images/bookstore-appservice-tests.png)
Congratulations, the **green icons** indicates that the tests have been successfully passed!
## The Next Part
See the [next part](part-5.md) of this tutorial.

401
docs/en/Tutorials/Part-5.md

@ -0,0 +1,401 @@
# Web Application Development Tutorial - Part 5: Authorization
````json
//[doc-params]
{
"UI": ["MVC","NG"],
"DB": ["EF","Mongo"]
}
````
{{
if UI == "MVC"
UI_Text="mvc"
else if UI == "NG"
UI_Text="angular"
else
UI_Text="?"
end
if DB == "EF"
DB_Text="Entity Framework Core"
else if DB == "Mongo"
DB_Text="MongoDB"
else
DB_Text="?"
end
}}
## About This Tutorial
In this tutorial series, you will build an ABP based web application named `Acme.BookStore`. This application is used to manage a list of books and their authors. It is developed using the following technologies:
* **{{DB_Text}}** as the ORM provider.
* **{{UI_Value}}** as the UI Framework.
This tutorial is organized as the following parts;
- [Part 1: Creating the project and book list page](Part-1.md)
- [Part 2: The book list page](Part-2.md)
- [Part 3: Creating, updating and deleting books](Part-3.md)
- [Part 4: Integration tests](Part-4.md)
- **Part 5: Authorization (this part)**
### Download the Source Code
This tutorials has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded:
* [MVC (Razor Pages) UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore)
* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
## Permissions
ABP Framework provides an [authorization system](../Authorization.md) based on the ASP.NET Core's [authorization infrastructure](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/introduction). One major feature added on top of the standard authorization infrastructure is the **permission system** which allows to define permissions and enable/disable per role, user or client.
### Permission Names
A permission must have a unique name (a `string`). The best way is to define it as a `const`, so we can reuse the permission name.
Open the `BookStorePermissions` class inside the `Acme.BookStore.Application.Contracts` project and change the content as shown below:
````csharp
namespace Acme.BookStore.Permissions
{
public static class BookStorePermissions
{
public const string GroupName = "BookStore";
public static class Books
{
public const string Default = GroupName + ".Books";
public const string Create = Default + ".Create";
public const string Edit = Default + ".Edit";
public const string Delete = Default + ".Delete";
}
}
}
````
This is a hierarchical way of defining permission names. For example, "create book" permission name was defined as `BookStore.Books.Create`.
### Permission Definitions
You should define permissions before using them.
Open the `BookStorePermissionDefinitionProvider` class inside the `Acme.BookStore.Application.Contracts` project and change the content as shown below:
````csharp
using Acme.BookStore.Localization;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Localization;
namespace Acme.BookStore.Permissions
{
public class BookStorePermissionDefinitionProvider : PermissionDefinitionProvider
{
public override void Define(IPermissionDefinitionContext context)
{
var bookStoreGroup = context.AddGroup(BookStorePermissions.GroupName, L("Permission:BookStore"));
var booksPermission = bookStoreGroup.AddPermission(BookStorePermissions.Books.Default, L("Permission:Books"));
booksPermission.AddChild(BookStorePermissions.Books.Create, L("Permission:Books.Create"));
booksPermission.AddChild(BookStorePermissions.Books.Edit, L("Permission:Books.Edit"));
booksPermission.AddChild(BookStorePermissions.Books.Delete, L("Permission:Books.Delete"));
}
private static LocalizableString L(string name)
{
return LocalizableString.Create<BookStoreResource>(name);
}
}
}
````
This class defines a **permission group** (to group permissions on the UI, will be seen below) and **4 permissions** inside this group. Also, **Create**, **Edit** and **Delete** are children of the `BookStorePermissions.Books.Default` permission. A child permission can be selected **only if the parent was selected**.
Finally, edit the localization file (`en.json` under the `Localization/BookStore` folder of the `Acme.BookStore.Domain.Shared` project) to define the localization keys used above:
````json
"Permission:BookStore": "Book Store",
"Permission:Books": "Book Management",
"Permission:Books.Create": "Creating new books",
"Permission:Books.Edit": "Editing the books",
"Permission:Books.Delete": "Deleting the books"
````
> Localization key names are arbitrary and no forcing rule. But we prefer the convention used above.
### Permission Management UI
Once you define the permissions, you can see them on the **permission management modal**.
Go to the *Administration -> Identity -> Roles* page, select *Permissions* action for the admin role to open the permission management modal:
![bookstore-permissions-ui](images/bookstore-permissions-ui.png)
Grant the permissions you want and save the modal.
## Authorization
Now, you can use the permissions to authorize the book management.
### Application Layer & HTTP API
Open the `BookAppService` class and add set the policy names as the permission names defined above:
````csharp
using System;
using Acme.BookStore.Permissions;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
namespace Acme.BookStore.Books
{
public class BookAppService :
CrudAppService<
Book, //The Book entity
BookDto, //Used to show books
Guid, //Primary key of the book entity
PagedAndSortedResultRequestDto, //Used for paging/sorting
CreateUpdateBookDto>, //Used to create/update a book
IBookAppService //implement the IBookAppService
{
public BookAppService(IRepository<Book, Guid> repository)
: base(repository)
{
GetPolicyName = BookStorePermissions.Books.Default;
GetListPolicyName = BookStorePermissions.Books.Default;
CreatePolicyName = BookStorePermissions.Books.Create;
UpdatePolicyName = BookStorePermissions.Books.Edit;
DeletePolicyName = BookStorePermissions.Books.Delete;
}
}
}
````
Added code to the constructor. Base `CrudAppService` automatically uses these permissions on the CRUD operations. This makes the **application service** secure, but also makes the **HTTP API** secure since this service is automatically used as an HTTP API as explained before (see [auto API controllers](../API/Auto-API-Controllers.md)).
{{if UI == "MVC"}}
### Razor Page
While securing the HTTP API & the application service prevents unauthorized users to use the services, they can still navigate to the book management page. While they will get authorization exception when the page makes the first AJAX call to the server, we should also authorize the page for a better user experience and security.
Open the `BookStoreWebModule` and add the following code block inside the `ConfigureServices` method:
````csharp
Configure<RazorPagesOptions>(options =>
{
options.Conventions.AuthorizePage("/Books/Index", BookStorePermissions.Books.Default);
options.Conventions.AuthorizePage("/Books/CreateModal", BookStorePermissions.Books.Create);
options.Conventions.AuthorizePage("/Books/EditModal", BookStorePermissions.Books.Edit);
});
````
Now, unauthorized users are redirected to the **login page**.
#### Hide the New Book Button
The book management page has a *New Book* button that should be invisible if the current user has no *Book Creation* permission.
![bookstore-new-book-button-small](images/bookstore-new-book-button-small.png)
Open the `Pages/Books/Index.cshtml` file and change the content as shown below:
````html
@page
@using Acme.BookStore.Localization
@using Acme.BookStore.Permissions
@using Acme.BookStore.Web.Pages.Books
@using Microsoft.AspNetCore.Authorization
@using Microsoft.Extensions.Localization
@model IndexModel
@inject IStringLocalizer<BookStoreResource> L
@inject IAuthorizationService AuthorizationService
@section scripts
{
<abp-script src="/Pages/Books/Index.js"/>
}
<abp-card>
<abp-card-header>
<abp-row>
<abp-column size-md="_6">
<abp-card-title>@L["Books"]</abp-card-title>
</abp-column>
<abp-column size-md="_6" class="text-right">
@if (await AuthorizationService.IsGrantedAsync(BookStorePermissions.Books.Create))
{
<abp-button id="NewBookButton"
text="@L["NewBook"].Value"
icon="plus"
button-type="Primary"/>
}
</abp-column>
</abp-row>
</abp-card-header>
<abp-card-body>
<abp-table striped-rows="true" id="BooksTable"></abp-table>
</abp-card-body>
</abp-card>
````
* Added `@inject IAuthorizationService AuthorizationService` to access to the authorization service.
* Used `@if (await AuthorizationService.IsGrantedAsync(BookStorePermissions.Books.Create))` to check the book creation permission to conditionally render the *New Book* button.
### JavaScript Side
Books table in the book management page has an actions button for each row. The actions button includes *Edit* and *Delete* actions:
![bookstore-edit-delete-actions](images/bookstore-edit-delete-actions.png)
We should hide an action if the current user has not granted for the related permission. Datatables row actions has a `visible` option that can be set to `false` to hide the action item.
Open the `Pages/Books/Index.js` inside the `Acme.BookStore.Web` project and add a `visible` option to the `Edit` action as shown below:
````js
{
text: l('Edit'),
visible: abp.auth.isGranted('BookStore.Books.Edit'), //CHECK for the PERMISSION
action: function (data) {
editModal.open({ id: data.record.id });
}
}
````
Do same for the `Delete` action:
````js
visible: abp.auth.isGranted('BookStore.Books.Delete')
````
* `abp.auth.isGranted(...)` is used to check a permission that is defined before.
* `visible` could also be get a function that returns a `bool` if the value will be calculated later, based on some conditions.
### Menu Item
Even we have secured all the layers of the book management page, it is still visible on the main menu of the application. We should hide the menu item if the current user has no permission.
Open the `BookStoreMenuContributor` class, find the code block below:
````csharp
context.Menu.AddItem(
new ApplicationMenuItem(
"BooksStore",
l["Menu:BookStore"],
icon: "fa fa-book"
).AddItem(
new ApplicationMenuItem(
"BooksStore.Books",
l["Menu:Books"],
url: "/Books"
)
)
);
````
And replace this code block with the following:
````csharp
var bookStoreMenu = new ApplicationMenuItem(
"BooksStore",
l["Menu:BookStore"],
icon: "fa fa-book"
);
context.Menu.AddItem(bookStoreMenu);
//CHECK the PERMISSION
if (await context.IsGrantedAsync(BookStorePermissions.Books.Default))
{
bookStoreMenu.AddItem(new ApplicationMenuItem(
"BooksStore.Books",
l["Menu:Books"],
url: "/Books"
));
}
````
{{else if UI == "NG"}}
### Angular Guard Configuration
First step of the UI is to prevent unauthorized users to see the "Books" menu item and enter to the book management page.
Open the `/src/app/book/book-routing.module.ts` and replace with the following content:
````js
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AuthGuard, PermissionGuard } from '@abp/ng.core';
import { BookComponent } from './book.component';
const routes: Routes = [
{ path: '', component: BookComponent, canActivate: [AuthGuard, PermissionGuard] },
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class BookRoutingModule {}
````
* Imported `AuthGuard` and `PermissionGuard` from the `@abp/ng.core`.
* Added `canActivate: [AuthGuard, PermissionGuard]` to the route definition.
Open the `/src/app/route.provider.ts` and add `requiredPolicy: 'BookStore.Books'` to the `/books` route. The `/books` route block should be following:
````js
{
path: '/books',
name: '::Menu:Books',
parentName: '::Menu:BookStore',
layout: eLayoutType.application,
requiredPolicy: 'BookStore.Books',
}
````
### Hide the New Book Button
The book management page has a *New Book* button that should be invisible if the current user has no *Book Creation* permission.
![bookstore-new-book-button-small](images/bookstore-new-book-button-small.png)
Open the `/src/app/book/book.component.html` file and replace the create button HTML content as shown below:
````html
<!-- Add the abpPermission directive -->
<button abpPermission="BookStore.Books.Create" id="create" class="btn btn-primary" type="button" (click)="createBook()">
<i class="fa fa-plus mr-1"></i>
<span>{%{{{ '::NewBook' | abpLocalization }}}%}</span>
</button>
````
* Just added `abpPermission="BookStore.Books.Create"` that hides the button if the current user has no permission.
### Hide the Edit and Delete Actions
Books table in the book management page has an actions button for each row. The actions button includes *Edit* and *Delete* actions:
![bookstore-edit-delete-actions](images/bookstore-edit-delete-actions.png)
We should hide an action if the current user has not granted for the related permission.
Open the `/src/app/book/book.component.html` file and replace the edit and delete buttons contents as shown below:
````html
<!-- Add the abpPermission directive -->
<button abpPermission="BookStore.Books.Edit" ngbDropdownItem (click)="editBook(row.id)">
{%{{{ '::Edit' | abpLocalization }}}%}
</button>
<!-- Add the abpPermission directive -->
<button abpPermission="BookStore.Books.Delete" ngbDropdownItem (click)="delete(row.id)">
{%{{{ 'AbpAccount::Delete' | abpLocalization }}}%}
</button>
````
* Added `abpPermission="BookStore.Books.Edit"` that hides the edit action if the current user has no editing permission.
* Added `abpPermission="BookStore.Books.Delete"` that hides the delete action if the current user has no delete permission.
{{end}}

BIN
docs/en/Tutorials/images/bookstore-book-and-booktype.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 35 KiB

BIN
docs/en/Tutorials/images/bookstore-dbmigrator-on-solution.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
docs/en/Tutorials/images/bookstore-edit-button-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

BIN
docs/en/Tutorials/images/bookstore-edit-delete-actions.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
docs/en/Tutorials/images/bookstore-getlist-result-network.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
docs/en/Tutorials/images/bookstore-index-js-file-v3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

BIN
docs/en/Tutorials/images/bookstore-javascript-proxy-console.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

BIN
docs/en/Tutorials/images/bookstore-new-book-button-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

BIN
docs/en/Tutorials/images/bookstore-new-book-button-small.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
docs/en/Tutorials/images/bookstore-permissions-ui.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

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

@ -11,25 +11,24 @@ Create a new component that you want to use instead of an ABP component. Add tha
Then, open the `app.component.ts` and dispatch the `AddReplaceableComponent` action to replace your component with an ABP component as shown below:
```js
import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent action
import { AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent action
import { eIdentityComponents } from '@abp/ng.identity'; // imported eIdentityComponents enum
import { Store } from '@ngxs/store'; // imported Store
//...
@Component(/* component metadata */)
export class AppComponent implements OnInit {
constructor(..., private store: Store) {} // injected Store
ngOnInit() {
// added dispatch
export class AppComponent {
constructor(
private store: Store // injected Store
)
{
// dispatched the AddReplaceableComponent action
this.store.dispatch(
new AddReplaceableComponent({
component: YourNewRoleComponent,
key: eIdentityComponents.Roles,
}),
);
//...
}
}
```
@ -48,9 +47,7 @@ The example below describes how to replace the `ApplicationLayoutComponent`:
Run the following command to generate a layout in `angular` folder:
```bash
yarn ng generate component shared/my-application-layout --export --entryComponent
# You don't need the --entryComponent option in Angular 9
yarn ng generate component my-application-layout
```
Add the following code in your layout template (`my-layout.component.html`) where you want the page to be loaded.
@ -62,30 +59,29 @@ Add the following code in your layout template (`my-layout.component.html`) wher
Open `app.component.ts` in `src/app` folder and modify it as shown below:
```js
import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
import { AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents enum for component keys
import { MyApplicationLayoutComponent } from './shared/my-application-layout/my-application-layout.component'; // imported MyApplicationLayoutComponent
import { Store } from '@ngxs/store'; // imported Store
//...
import { MyApplicationLayoutComponent } from './my-application-layout/my-application-layout.component'; // imported MyApplicationLayoutComponent
@Component(/* component metadata */)
export class AppComponent implements OnInit {
constructor(..., private store: Store) {} // injected Store
ngOnInit() {
// added dispatch
export class AppComponent {
constructor(
private store: Store, // injected Store
) {
// dispatched the AddReplaceableComponent action
this.store.dispatch(
new AddReplaceableComponent({
component: MyApplicationLayoutComponent,
key: eThemeBasicComponents.ApplicationLayout,
}),
);
//...
}
}
```
> If you like to replace a layout component at runtime (e.g: changing the layout by pressing a button), pass the second parameter of the AddReplaceableComponent action as true. DynamicLayoutComponent loads content using a router-outlet. When the second parameter of AddReplaceableComponent is true, the route will be refreshed, so use it with caution. Your component state will be gone and any initiation logic (including HTTP requests) will be repeated.
### Layout Components
![Layout Components](./images/layout-components.png)

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

@ -201,77 +201,6 @@ this.config.dispatchGetAppConfiguration();
Note that **you do not have to call this method at application initiation**, because the application configuration is already being received from the server at start.
### How to Patch Route Configuration
The `dispatchPatchRouteByName` finds a route by its name and replaces its configuration in the `Store` with the new configuration passed as the second parameter.
```js
// this.config is instance of ConfigStateService
const newRouteConfig: Partial<ABP.Route> = {
name: "Home",
path: "home",
children: [
{
name: "Dashboard",
path: "dashboard"
}
]
};
this.config.dispatchPatchRouteByName("::Menu:Home", newRouteConfig);
// returns a state stream which emits after dispatch action is complete
```
### How to Add a New Route Configuration
The `dispatchAddRoute` adds a new route to the configuration state in the `Store`. For this, the route config should be passed as the parameter of the method.
```js
// this.config is instance of ConfigStateService
const newRoute: ABP.Route = {
name: "My New Page",
iconClass: "fa fa-dashboard",
path: "page",
invisible: false,
order: 2,
requiredPolicy: "MyProjectName.MyNewPage"
};
this.config.dispatchAddRoute(newRoute);
// returns a state stream which emits after dispatch action is complete
```
The `newRoute` will be placed as at root level, i.e. without any parent routes and its url will be stored as `'/path'`.
If you want **to add a child route, you can do this:**
```js
import { eIdentityRouteNames } from '@abp/ng.identity';
// this.config is instance of ConfigStateService
const newRoute: ABP.Route = {
parentName: eIdentityRouteNames.IdentityManagement,
name: "My New Page",
iconClass: "fa fa-dashboard",
path: "page",
invisible: false,
order: 2,
requiredPolicy: "MyProjectName.MyNewPage"
};
this.config.dispatchAddRoute(newRoute);
// returns a state stream which emits after dispatch action is complete
```
The `newRoute` will then be placed as a child of the parent route named `eIdentityRouteNames.IdentityManagement` and its url will be set as `'/identity/page'`.
#### Route Configuration Properties
Please refer to `ABP.Route` type for all the properties you can pass to `dispatchSetEnvironment` in its parameter. It can be found in the [common.ts file](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/common.ts#L27).
### How to Set the Environment
The `dispatchSetEnvironment` places environment variables passed to it in the `Store` under the configuration state. Here is how it is used:

73
docs/en/UI/Angular/Migration-Guide-v3.md

@ -18,7 +18,7 @@ This will make the following modifications:
- Update your package.json and install new packages
- Revise tsconfig.json files to create a "Solution Style" configuration
- Rename `browserlist` as `.browserlistrc`
- Rename `browserslist` as `.browserslistrc`
On the other hand, it would be better if you check which packages to update first with `yarn ng update` command alone. Angular will give you a list of packages to update.
@ -145,42 +145,33 @@ export class AppModule {}
AppRoutingModule:
```js
import { DynamicLayoutComponent } from '@abp/ng.core';
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [
{
path: '',
component: DynamicLayoutComponent,
children: [
{
path: '',
pathMatch: 'full',
loadChildren: () => import('./home/home.module')
.then(m => m.HomeModule),
},
{
path: 'account',
loadChildren: () => import('@abp/ng.account')
.then(m => m.AccountModule.forLazy({ redirectUrl: '/' })),
},
{
path: 'identity',
loadChildren: () => import('@abp/ng.identity')
.then(m => m.IdentityModule.forLazy()),
},
{
path: 'tenant-management',
loadChildren: () => import('@abp/ng.tenant-management')
.then(m => m.TenantManagementModule.forLazy()),
},
{
path: 'setting-management',
loadChildren: () => import('@abp/ng.setting-management')
.then(m => m.SettingManagementModule.forLazy()),
},
],
pathMatch: 'full',
loadChildren: () => import('./home/home.module').then(m => m.HomeModule),
},
{
path: 'account',
loadChildren: () =>
import('@abp/ng.account').then(m => m.AccountModule.forLazy({ redirectUrl: '/' })),
},
{
path: 'identity',
loadChildren: () => import('@abp/ng.identity').then(m => m.IdentityModule.forLazy()),
},
{
path: 'tenant-management',
loadChildren: () =>
import('@abp/ng.tenant-management').then(m => m.TenantManagementModule.forLazy()),
},
{
path: 'setting-management',
loadChildren: () =>
import('@abp/ng.setting-management').then(m => m.SettingManagementModule.forLazy()),
},
];
@ -191,7 +182,23 @@ const routes: Routes = [
export class AppRoutingModule {}
```
> You may have noticed that we used `DynamicLayoutComponent` at top level route component. We made this change in order to avoid unnecessary renders and flickering. It is not mandatory, but we recommend doing the same in your app routing.
AppComponent:
```js
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<abp-loader-bar></abp-loader-bar>
<abp-dynamic-layout></abp-dynamic-layout>
`,
})
export class AppComponent {}
```
> You may have noticed that we used `<abp-dynamic-layout>` instead of `<router-outlet>` in the AppComponent template. We made this change in order to avoid unnecessary renders and flickering. It is not mandatory, but we recommend doing the same in your AppComponent.
#### What to Do When Migrating?
@ -201,7 +208,7 @@ export class AppRoutingModule {}
- Call static `forRoot` method of `ThemeBasicModule` (or `ThemeLeptonModule` if commercial) and remove `SharedModule` from imports (unless you have added anything that is necessary for your root module in it).
- Import lazy ABP modules directly in app routing module (e.g. `() => import('@abp/ng.identity').then(...)`).
- Call static `forLazy` method of all lazy modules inside `then`, even if a configuration is not passed.
- [OPTIONAL] Add an empty parent route with `DynamicLayoutComponent` for better performance and UX.
- [OPTIONAL] Add the `<abp-dynamic-layout></abp-dynamic-layout>` to the AppComponent template and remove the `<router-outlet></router-outlet>` for better performance and UX.
### RoutesService

20
docs/en/docs-nav.json

@ -26,19 +26,27 @@
"text": "Tutorials",
"items": [
{
"text": "Application Development",
"text": "Web Application Development",
"items": [
{
"text": "Part-1: Creating a new solution and listing items",
"text": "1: Creating the Server Side",
"path": "Tutorials/Part-1.md"
},
{
"text": "Part-2: CRUD operations",
"text": "2: The Book List Page",
"path": "Tutorials/Part-2.md"
},
{
"text": "Part-3: Integration tests",
"text": "3: Creating, Updating and Deleting Books",
"path": "Tutorials/Part-3.md"
},
{
"text": "4: Integration Tests",
"path": "Tutorials/Part-4.md"
},
{
"text": "5: Authorization",
"path": "Tutorials/Part-5.md"
}
]
}
@ -618,6 +626,10 @@
{
"text": "API Documentation",
"path": "{ApiDocumentationUrl}"
},
{
"text": "Official Packages",
"path": "https://abp.io/packages"
}
]
}

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

@ -195,7 +195,7 @@ Abra `book-list.component.html`e adicione o formulário no modelo de corpo do mo
<label for="book-type">Type</label><span> * </span>
<select class="form-control" id="book-type" formControlName="type">
<option [ngValue]="null">Select a book type</option>
<option [ngValue]="booksType[type]" *ngFor="let type of bookTypeArr"> {%{{{ type }}}%}</option>
<option [ngValue]="booksType[type]" *ngFor="let type of bookTypes"> {%{{{ type }}}%}</option>
</select>
</div>
@ -218,18 +218,18 @@ Abra `book-list.component.html`e adicione o formulário no modelo de corpo do mo
> Usamos o [datepicker do NgBootstrap](https://ng-bootstrap.github.io/#/components/datepicker/overview) neste componente.
Abra o `book-list.component.ts`e crie uma matriz chamada `bookTypeArr`:
Abra o `book-list.component.ts`e crie uma matriz chamada `bookTypes`:
```js
//...
form: FormGroup;
bookTypeArr = Object.keys(Books.BookType).filter(
bookTypes = Object.keys(Books.BookType).filter(
bookType => typeof this.booksType[bookType] === 'number'
);
```
O `bookTypeArr`contém os campos da `BookType`enumeração. A matriz resultante é mostrada abaixo:
O `bookTypes`contém os campos da `BookType`enumeração. A matriz resultante é mostrada abaixo:
```js
['Adventure', 'Biography', 'Dystopia', 'Fantastic' ...]

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

@ -66,9 +66,7 @@ public class YourModule : AbpModule
}
````
1. 我们需要在 `Module` 类的 `OnApplicationInitialization` 方法添加 `UseHangfireServer` 方法调用.
如果你想要使用Hangfire的面板,你可以使用: `UseHangfireDashboard`
1. 如果你想要使用Hangfire的面板,你可以在 `Module` 类的 `OnApplicationInitialization` 方法添加: `UseHangfireDashboard`
````csharp
public override void OnApplicationInitialization(ApplicationInitializationContext context)
@ -77,7 +75,6 @@ public class YourModule : AbpModule
// ... others
app.UseHangfireServer();
app.UseHangfireDashboard();
}

8
docs/zh-Hans/Local-Event-Bus.md

@ -203,9 +203,9 @@ namespace AbpDemo
事件类型;
* `EntityCreatedEventData<T>` 当实体创建创建成功后发布.
* `EntityUpdatedEventData<T>` 当实体创建更新成功后发布.
* `EntityDeletedEventData<T>` 当实体创建删除成功后发布.
* `EntityCreatedEventData<T>` 当实体创建成功后发布.
* `EntityUpdatedEventData<T>` 当实体更新成功后发布.
* `EntityDeletedEventData<T>` 当实体删除成功后发布.
* `EntityChangedEventData<T>` 当实体创建,更新,删除后发布. 如果你需要监听任何类型的更改,它是一种快捷方式 - 而不是订阅单个事件.
### 用于进行时态事件
@ -224,4 +224,4 @@ namespace AbpDemo
在将更改保存到数据库时发布预构建事件;
* 对于 EF Core, 他们在 `DbContext.SaveChanges` 发布.
* 对于 MongoDB, 在你调用仓储的 `InsertAsync`, `UpdateAsync``DeleteAsync` 方法发布(因为MongoDB没有更改追踪系统).
* 对于 MongoDB, 在你调用仓储的 `InsertAsync`, `UpdateAsync``DeleteAsync` 方法发布(因为MongoDB没有更改追踪系统).

4
docs/zh-Hans/Repositories.md

@ -248,6 +248,4 @@ ABP框架使用实际数据库提供程序的API异步执行查询.虽然这不
* 如果你正在构建一个没有数据库提供程序集成包的**可重用库**,但是在某些情况下需要执行 `IQueryable<T>`对象.
For example, ABP Framework uses the `IAsyncQueryableExecuter` in the `CrudAppService` base class (see the [application services](Application-Services.md) document).
例如,ABP框架在 `CrudAppService` 基类中(参阅[应用程序](Application-Services.md)文档)使用 `IAsyncQueryableExecuter`.
例如,ABP框架在 `CrudAppService` 基类中(参阅[应用程序](Application-Services.md)文档)使用 `IAsyncQueryableExecuter`.

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

@ -650,7 +650,7 @@ export class BookListComponent implements OnInit {
<label for="book-type">Type</label><span> * </span>
<select class="form-control" id="book-type" formControlName="type">
<option [ngValue]="null">Select a book type</option>
<option [ngValue]="bookType[type]" *ngFor="let type of bookTypeArr"> {%{{{ type }}}%}</option>
<option [ngValue]="bookType[type]" *ngFor="let type of bookTypes"> {%{{{ type }}}%}</option>
</select>
</div>
@ -721,8 +721,8 @@ export class BookListComponent implements OnInit {
booksType = BookType;
// <== added bookTypeArr array ==>
bookTypeArr = Object.keys(BookType).filter(
// <== added bookTypes array ==>
bookTypes = Object.keys(BookType).filter(
(bookType) => typeof this.booksType[bookType] === 'number'
);
@ -764,7 +764,7 @@ export class BookListComponent implements OnInit {
* 我们添加了一个新的 `NgbDateAdapter` 提供程序,它将Datepicker值转换为Date类型. 有关更多详细信息,请参见[datepicker adapters](https://ng-bootstrap.github.io/#/components/datepicker/overview).
* 我们添加了 `bookTypeArr` 数组,以便能够在combobox值中使用它. `bookTypeArr` 包含 `BookType` 枚举的字段. 得到的数组如下所示:
* 我们添加了 `bookTypes` 数组,以便能够在combobox值中使用它. `bookTypes` 包含 `BookType` 枚举的字段. 得到的数组如下所示:
```js
['Adventure', 'Biography', 'Dystopia', 'Fantastic' ...]
@ -799,7 +799,7 @@ export class BookListComponent implements OnInit {
booksType = BookType;
bookTypeArr = Object.keys(BookType).filter(
bookTypes = Object.keys(BookType).filter(
(bookType) => typeof this.booksType[bookType] === 'number'
);
@ -904,7 +904,7 @@ export class BookListComponent implements OnInit {
booksType = BookType;
bookTypeArr = Object.keys(BookType).filter(
bookTypes = Object.keys(BookType).filter(
(bookType) => typeof this.booksType[bookType] === 'number'
);

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

@ -11,25 +11,24 @@
然后打开 `app.component.ts` 使用 `AddReplaceableComponent` 将你的组件替换ABP组件. 如下所示:
```js
import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent action
import { AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent action
import { eIdentityComponents } from '@abp/ng.identity'; // imported eIdentityComponents enum
import { Store } from '@ngxs/store'; // imported Store
//...
@Component(/* component metadata */)
export class AppComponent implements OnInit {
constructor(..., private store: Store) {} // injected Store
ngOnInit() {
// added dispatch
export class AppComponent {
constructor(
private store: Store // injected Store
)
{
// dispatched the AddReplaceableComponent action
this.store.dispatch(
new AddReplaceableComponent({
component: YourNewRoleComponent,
key: eIdentityComponents.Roles,
}),
);
//...
}
}
```
@ -47,9 +46,7 @@ export class AppComponent implements OnInit {
运行以下命令在 `angular` 文件夹中生成布局:
```bash
yarn ng generate component shared/my-application-layout --export --entryComponent
# You don't need the --entryComponent option in Angular 9
yarn ng generate component my-application-layout
```
在你的布局模板(`my-layout.component.html`)中添加以下代码:
@ -61,26 +58,23 @@ yarn ng generate component shared/my-application-layout --export --entryComponen
打开 `src/app` 文件夹下的 `app.component.ts` 文件添加以下内容:
```js
import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
import { AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents enum for component keys
import { MyApplicationLayoutComponent } from './shared/my-application-layout/my-application-layout.component'; // imported MyApplicationLayoutComponent
import { Store } from '@ngxs/store'; // imported Store
//...
import { MyApplicationLayoutComponent } from './my-application-layout/my-application-layout.component'; // imported MyApplicationLayoutComponent
@Component(/* component metadata */)
export class AppComponent implements OnInit {
constructor(..., private store: Store) {} // injected Store
ngOnInit() {
// added dispatch
export class AppComponent {
constructor(
private store: Store, // injected Store
) {
// dispatched the AddReplaceableComponent action
this.store.dispatch(
new AddReplaceableComponent({
component: MyApplicationLayoutComponent,
key: eThemeBasicComponents.ApplicationLayout,
}),
);
//...
}
}
```

1
docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Form-elements.md

@ -0,0 +1 @@
TODO...

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

@ -34,7 +34,7 @@
"path": "Tutorials/Part-1.md"
},
{
"text": "第章: 增删改查操作",
"text": "第章: 增删改查操作",
"path": "Tutorials/Part-2.md"
},
{

27
framework/src/Volo.Abp.AspNetCore.Authentication.OAuth/Properties/launchSettings.json

@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:53601/",
"sslPort": 44330
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Volo.Abp.AspNetCore.Authentication.OAuth": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:5001;http://localhost:5000"
}
}
}

27
framework/src/Volo.Abp.AspNetCore.MultiTenancy/Properties/launchSettings.json

@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:53597/",
"sslPort": 44355
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Volo.Abp.AspNetCore.MultiTenancy": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:5001;http://localhost:5000"
}
}
}

27
framework/src/Volo.Abp.AspNetCore.Mvc.Client/Properties/launchSettings.json

@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:53603/",
"sslPort": 44305
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Volo.Abp.AspNetCore.Mvc.Client": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:5001;http://localhost:5000"
}
}
}

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

@ -1,4 +1,4 @@
{
{
"culture": "nl",
"texts": {
"GivenTenantIsNotAvailable": "Gegeven klant is niet beschikbaar: {0}",

31
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/layout.css

@ -57,7 +57,34 @@
font-size: 0.8em;
}
.dataTables_scrollBody {
min-height: 248px;
}
}
div.dataTables_wrapper div.dataTables_info {
padding-top: 11px;
white-space: nowrap;
}
div.dataTables_wrapper div.dataTables_length label {
padding-top: 10px;
margin-bottom: 0;
}
.rtl .dropdown-menu-right {
right: auto;
left: 0;
}
.rtl .dropdown-menu-right a {
text-align: right;
}
.rtl .navbar .dropdown-menu a {
text-align: right;
}
.rtl .navbar .dropdown-submenu .dropdown-menu {
top: 0;
left: auto;
right: 100%;
}

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/AbpAspNetCoreMvcUiThemeSharedDemoModule.cs

@ -12,7 +12,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo
{
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<AbpAspNetCoreMvcUiThemeSharedDemoModule>("Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo");
options.FileSets.AddEmbedded<AbpAspNetCoreMvcUiThemeSharedDemoModule>();
});
}
}

10
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.csproj

@ -12,6 +12,7 @@
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
<IsPackable>true</IsPackable>
<OutputType>Library</OutputType>
</PropertyGroup>
@ -23,5 +24,14 @@
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="3.1.5" />
</ItemGroup>
<ItemGroup>
<Content Remove="Views\Components\Themes\Shared\**\*.cshtml" />
<EmbeddedResource Include="Views\Components\Themes\Shared\**\*.cshtml" />
</ItemGroup>
</Project>

27
framework/src/Volo.Abp.AspNetCore.Serilog/Properties/launchSettings.json

@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:53606/",
"sslPort": 44319
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Volo.Abp.AspNetCore.Serilog": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:5001;http://localhost:5000"
}
}
}

27
framework/src/Volo.Abp.AspNetCore.SignalR/Properties/launchSettings.json

@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:53611/",
"sslPort": 44331
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Volo.Abp.AspNetCore.SignalR": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:5001;http://localhost:5000"
}
}
}

27
framework/src/Volo.Abp.AspNetCore.TestBase/Properties/launchSettings.json

@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:53599/",
"sslPort": 44358
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Volo.Abp.AspNetCore.TestBase": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:5001;http://localhost:5000"
}
}
}

27
framework/src/Volo.Abp.AspNetCore/Properties/launchSettings.json

@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:53596/",
"sslPort": 44300
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Volo.Abp.AspNetCore": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:5001;http://localhost:5000"
}
}
}

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

@ -15,11 +15,11 @@ namespace Volo.Abp.AspNetCore.ExceptionHandling
{
public class AbpExceptionHandlingMiddleware : IMiddleware, ITransientDependency
{
private readonly ILogger<AbpUnitOfWorkMiddleware> _logger;
private readonly ILogger<AbpExceptionHandlingMiddleware> _logger;
private readonly Func<object, Task> _clearCacheHeadersDelegate;
public AbpExceptionHandlingMiddleware(ILogger<AbpUnitOfWorkMiddleware> logger)
public AbpExceptionHandlingMiddleware(ILogger<AbpExceptionHandlingMiddleware> logger)
{
_logger = logger;

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

@ -53,7 +53,7 @@ namespace Volo.Abp.Cli.Commands
var apiUrl = commandLineArgs.Options.GetOrNull(Options.ApiUrl.Short, Options.ApiUrl.Long);
if (string.IsNullOrWhiteSpace(apiUrl))
{
var environmentJson = File.ReadAllText("projects/dev-app/src/environments/environment.ts").Split("export const environment = ")[1].Replace(";", " ");
var environmentJson = File.ReadAllText("src/environments/environment.ts").Split("export const environment = ")[1].Replace(";", " ");
var environment = JObject.Parse(environmentJson);
apiUrl = environment["apis"]["default"]["url"].ToString();
}
@ -294,7 +294,7 @@ namespace Volo.Abp.Cli.Commands
if (firstType == "List" && !File.Exists(secondTypeModelPath))
{
secondType = "any";
}
}
serviceFileText.AppendLine(
firstType == "List"
@ -322,13 +322,15 @@ namespace Volo.Abp.Cli.Commands
"String" => "string",
"IActionResult" => "void",
"ActionResult" => "void",
"Int64" => "number",
"Int32" => "number",
_ => type
};
};
serviceFileText.AppendLine(
$" {actionName}({parametersText}): Observable<{type}> {{");
if (type != "void" && type != "string")
if (type != "void" && type != "string" && type != "number")
{
secondTypeList.Add(type);
}
@ -503,6 +505,11 @@ namespace Volo.Abp.Cli.Commands
var path = output + $"{outputPrefix}/{rootPath}/{controllerPathName}/models/{typeModelName}";
if (File.Exists(path))
{
return null;
}
var modelFileText = new StringBuilder();
var baseType = (string)type["baseType"];

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

@ -104,7 +104,7 @@ namespace Volo.Abp.Cli.Commands
Logger.LogInformation("Template Source: " + templateSource);
}
var createSolutionFolder = (commandLineArgs.Options.GetOrNull(Options.CreateSolutionFolder.Short, Options.CreateSolutionFolder.Long) ?? "true").ToLowerInvariant() != "false";
var createSolutionFolder = GetCreateSolutionFolderPreference(commandLineArgs);
if (!createSolutionFolder)
{
Logger.LogInformation("Create Solution Folder: no");
@ -183,6 +183,18 @@ namespace Volo.Abp.Cli.Commands
Logger.LogInformation($"'{projectName}' has been successfully created to '{outputFolder}'");
}
private bool GetCreateSolutionFolderPreference(CommandLineArgs commandLineArgs)
{
var longKey = commandLineArgs.Options.ContainsKey(Options.CreateSolutionFolder.Long);
if (longKey == false)
{
return commandLineArgs.Options.ContainsKey(Options.CreateSolutionFolder.Short);
}
return longKey;
}
private static string GetConnectionString(CommandLineArgs commandLineArgs)
{
var connectionString = commandLineArgs.Options.GetOrNull(Options.ConnectionString.Short, Options.ConnectionString.Long);

3
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/SolutionRenameStep.cs

@ -64,7 +64,8 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
RenameHelper.RenameAll(_entries, _projectNamePlaceHolder, _projectName);
RenameHelper.RenameAll(_entries, _projectNamePlaceHolder.ToCamelCase(), _projectName.ToCamelCase());
RenameHelper.RenameAll(_entries, _projectNamePlaceHolder.ToKebabCase(), _projectName.ToKebabCase());
RenameHelper.RenameAll(_entries, _projectNamePlaceHolder.ToSnakeCase().ToUpper(), _projectName.ToSnakeCase().ToUpper());
}
}
}
}
}

6
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AngularEnvironmentFilePortChangeForSeparatedIdentityServersStep.cs

@ -10,9 +10,9 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.App
{
var fileEntries = context.Files.Where(x =>
!x.IsDirectory &&
(x.Name.EndsWith("angular/projects/dev-app/src/environments/environment.ts", StringComparison.InvariantCultureIgnoreCase) ||
x.Name.EndsWith("angular/projects/dev-app/src/environments/environment.hmr.ts", StringComparison.InvariantCultureIgnoreCase) ||
x.Name.EndsWith("angular/projects/dev-app/src/environments/environment.prod.ts", StringComparison.InvariantCultureIgnoreCase))
(x.Name.EndsWith("angular/src/environments/environment.ts", StringComparison.InvariantCultureIgnoreCase) ||
x.Name.EndsWith("angular/src/environments/environment.hmr.ts", StringComparison.InvariantCultureIgnoreCase) ||
x.Name.EndsWith("angular/src/environments/environment.prod.ts", StringComparison.InvariantCultureIgnoreCase))
)
.ToList();

72
framework/src/Volo.Abp.Core/System/AbpStringExtensions.cs

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
@ -312,6 +313,75 @@ namespace System
: Regex.Replace(str, "[a-z][A-Z]", m => m.Value[0] + "-" + char.ToLowerInvariant(m.Value[1]));
}
/// <summary>
/// Converts given PascalCase/camelCase string to snake case.
/// Example: "ThisIsSampleSentence" is converted to "this_is_a_sample_sentence".
/// https://github.com/npgsql/npgsql/blob/dev/src/Npgsql/NameTranslation/NpgsqlSnakeCaseNameTranslator.cs#L51
/// </summary>
/// <param name="str">String to convert.</param>
/// <returns></returns>
public static string ToSnakeCase(this string str)
{
if (string.IsNullOrWhiteSpace(str))
{
return str;
}
var builder = new StringBuilder(str.Length + Math.Min(2, str.Length / 5));
var previousCategory = default(UnicodeCategory?);
for (var currentIndex = 0; currentIndex < str.Length; currentIndex++)
{
var currentChar = str[currentIndex];
if (currentChar == '_')
{
builder.Append('_');
previousCategory = null;
continue;
}
var currentCategory = char.GetUnicodeCategory(currentChar);
switch (currentCategory)
{
case UnicodeCategory.UppercaseLetter:
case UnicodeCategory.TitlecaseLetter:
if (previousCategory == UnicodeCategory.SpaceSeparator ||
previousCategory == UnicodeCategory.LowercaseLetter ||
previousCategory != UnicodeCategory.DecimalDigitNumber &&
previousCategory != null &&
currentIndex > 0 &&
currentIndex + 1 < str.Length &&
char.IsLower(str[currentIndex + 1]))
{
builder.Append('_');
}
currentChar = char.ToLower(currentChar);
break;
case UnicodeCategory.LowercaseLetter:
case UnicodeCategory.DecimalDigitNumber:
if (previousCategory == UnicodeCategory.SpaceSeparator)
{
builder.Append('_');
}
break;
default:
if (previousCategory != null)
{
previousCategory = UnicodeCategory.SpaceSeparator;
}
continue;
}
builder.Append(currentChar);
previousCategory = currentCategory;
}
return builder.ToString();
}
/// <summary>
/// Converts string to enum value.
/// </summary>
@ -476,4 +546,4 @@ namespace System
return encoding.GetBytes(str);
}
}
}
}

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

@ -1,6 +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."
"MaxResultCountExceededExceptionMessage": "{0} kann nicht mehr als {1} sein! Erhöhen Sie {2}.{3} auf der Serverseite, um mehr Ergebnisse zu ermöglichen."
}
}

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

@ -12,12 +12,12 @@
"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.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."
}
}

27
framework/src/Volo.Abp.Http.Client.IdentityModel.Web/Properties/launchSettings.json

@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:53610/",
"sslPort": 44304
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Volo.Abp.Http.Client.IdentityModel.Web": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:5001;http://localhost:5000"
}
}
}

2
framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TZConvertTimezoneProvider.cs

@ -10,7 +10,7 @@ namespace Volo.Abp.Timing
{
public virtual List<NameValue> GetWindowsTimezones()
{
return TZConvert.KnownIanaTimeZoneNames.OrderBy(x => x).Select(x => new NameValue(x, x)).ToList();
return TZConvert.KnownWindowsTimeZoneIds.OrderBy(x => x).Select(x => new NameValue(x, x)).ToList();
}
public virtual List<NameValue> GetIanaTimezones()

2
framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json

@ -47,6 +47,8 @@
"PagerInfoEmpty": "Zobrazeno od 0 do 0 z celkem 0 záznamů",
"PagerInfoFiltered": "(filtrováno ze všech _MAX_ záznamů)",
"NoDataAvailableInDatatable": "Nejsou žádná data",
"Total": "celkem",
"Selected": "vybráno",
"PagerShowMenuEntries": "Zobrazit _MENU_ záznamů",
"DatatableActionDropdownDefaultText": "Akce",
"ChangePassword": "Změnit heslo",

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

@ -1,18 +1,18 @@
{
"culture": "de",
"texts": {
"InternalServerErrorMessage": "Während Ihrer Anfrage ist ein interner Fehler aufgetreten!",
"ValidationErrorMessage": "Ihre Anfrage ist nicht gültig!",
"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.",
"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!",
"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}!",
"EntityNotFoundErrorMessage": "Es gibt keine Entität {0} mit id = {1}!",
"Languages": "Sprachen",
"Error": "Fehler",
"AreYouSure": "Sind Sie sicher?",
@ -20,11 +20,11 @@
"Yes": "Ja",
"No": "Nein",
"Ok": "Ok",
"Close": "Schließen",
"Close": "Schließen",
"Save": "Speichern",
"SavingWithThreeDot": "Speichere...",
"Actions": "Aktionen",
"Delete": "Löschen",
"Delete": "Löschen",
"Edit": "Bearbeiten",
"Refresh": "Aktualisieren",
"Language": "Sprache",
@ -36,28 +36,28 @@
"Register": "Registrieren",
"Logout": "Abmelden",
"Submit": "Absenden",
"Back": "Zurück",
"Back": "Zurück",
"PagerSearch": "Suchen",
"PagerNext": "Nächste",
"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",
"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",
"ChangePassword": "Passwort ändern",
"PersonalInfo": "Mein Profil",
"AreYouSureYouWantToCancelEditingWarningMessage": "Sie haben ungespeicherte Änderungen.",
"AreYouSureYouWantToCancelEditingWarningMessage": "Sie haben ungespeicherte Änderungen.",
"UnhandledException": "Unerwartete Ausnahme!",
"401Message": "Unauthorisiert",
"403Message": "Verboten",
"404Message": "Seite nicht gefunden",
"500Message": "Internet Server Fehler",
"500Message": "Interner Server Fehler",
"GoHomePage": "Zur Startseite",
"GoBack": "Zurück"
"GoBack": "Zurück"
}
}

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

@ -1,34 +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.",
"'{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 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 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.",
"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.",
"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.",
"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."
"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/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/de.json

@ -2,6 +2,6 @@
"culture": "de",
"texts": {
"BirthDate": "Geburtsdatum",
"Value1": "Wert Eins"
"Value1": "Wert Eins"
}
}

6
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/Pages/Components/Dropdowns/Index.cshtml

@ -4,10 +4,10 @@
@model Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo.Pages.Components.Dropdowns.IndexModel
@inject IPageLayout PageLayout
@{
PageLayout.Content.Title = "DrowDowns";
PageLayout.Content.Title = "Dropdowns";
}
<h2>Cards</h2>
<p>Based on <a href="https://getbootstrap.com/docs/4.1/components/dropdowns/" target="_blank"> Bootstrap button</a>.</p>
<h2>Dropdowns</h2>
<p>Based on <a href="https://getbootstrap.com/docs/4.1/components/dropdowns/" target="_blank"> Bootstrap Dropdown</a>.</p>
<p>Check the <a href="https://docs.abp.io/en/abp/latest/UI/AspNetCore/Tag-Helpers/Dropdowns" target="_blank">ABP Documentation</a>.</p>
@await Component.InvokeAsync(typeof(DropdownsDemoViewComponent))

12
framework/test/Volo.Abp.Core.Tests/System/StringExtensions_Tests.cs

@ -83,6 +83,16 @@ namespace System
"ThisIsSampleText".ToKebabCase().ShouldBe("this-is-sample-text");
}
[Fact]
public void ToSnakeCase_Test()
{
(null as string).ToSnakeCase().ShouldBe(null);
"helloMoon".ToSnakeCase().ShouldBe("hello_moon");
"HelloWorld".ToSnakeCase().ShouldBe("hello_world");
"HelloIsparta".ToSnakeCase().ShouldBe("hello_isparta");
"ThisIsSampleText".ToSnakeCase().ShouldBe("this_is_sample_text");
}
[Fact]
public void ToSentenceCase_Test()
{
@ -247,4 +257,4 @@ namespace System
_cultureScope.Dispose();
}
}
}
}

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

@ -148,7 +148,10 @@ namespace Volo.Abp.Localization
_localizer["MaxLenghtErrorMessage", 42].Value.ShouldBe("El campo puede tener un máximo de '42' caracteres"); //Overriden in Source/es.json
}
using (CultureHelper.Use("de"))
{
_localizer["MaxLenghtErrorMessage", 42].Value.ShouldBe("Die Länge dieses Feldes kann maximal '42'-Zeichen betragen"); //Overriden in Source/es.json
}
}
[Fact]

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

@ -1,4 +1,4 @@
{
{
"culture": "nl",
"texts": {
"USA": "Verenigde Staten van Amerika",

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

@ -1,4 +1,4 @@
{
{
"culture": "nl",
"texts": {
"ThisFieldIsRequired": "Dit veld is verplicht",

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

@ -1,4 +1,4 @@
{
{
"culture": "nl",
"texts": {
"Hello <b>{0}</b>.": "Hallo <b>{0}</b>.",

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

@ -1,4 +1,4 @@
{
{
"culture": "nl",
"texts": {
"SeeYou": "Tot ziens"

5
modules/account/.prettierrc

@ -0,0 +1,5 @@
{
"singleQuote": true,
"useTabs": false,
"tabWidth": 4
}

5
modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/cs.json

@ -40,6 +40,9 @@
"DisplayName:Abp.Account.IsSelfRegistrationEnabled": "Je povolena automatická registrace",
"Description:Abp.Account.IsSelfRegistrationEnabled": "Zda si uživatel může účet zaregistrovat sám.",
"DisplayName:Abp.Account.EnableLocalLogin": "Ověření pomocí místního účtu",
"Description:Abp.Account.EnableLocalLogin": "Označuje, zda server umožní uživatelům ověřovat se pomocí místního účtu."
"Description:Abp.Account.EnableLocalLogin": "Označuje, zda server umožní uživatelům ověřovat se pomocí místního účtu.",
"LoggedOutTitle": "Odhlášeno",
"LoggedOutText": "Byli jste odhlášeni a brzy budete přesměrováni.",
"ReturnToText": "Kliknutím sem přejdete na {0}"
}
}

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

@ -8,22 +8,22 @@
"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.",
"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.",
"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:NewPasswordConfirm": "Neues Passwort bestätigen",
"PasswordChangedMessage": "Ihr Passwort wurde erfolgreich geändert.",
"DisplayName:UserName": "Benutzername",
"DisplayName:Email": "E-Mail",
"DisplayName:Name": "Name",
@ -31,12 +31,12 @@
"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.",
"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",
"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",

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

@ -11,12 +11,12 @@
}
@section scripts {
<abp-script-bundle name="@typeof(LoggedOutModel).FullName">
<abp-script src="/Pages/Account/LoggedOut.js"/>
<abp-script src="/Pages/Account/LoggedOut.js" />
</abp-script-bundle>
}
@section styles {
<abp-style src="/Pages/Account/LoggedOut.css"/>
<abp-style src="/Pages/Account/LoggedOut.css" />
}
<abp-card>
@ -32,4 +32,4 @@
<iframe class="signout logoutiframe" src="@Model.SignOutIframeUrl"></iframe>
}
</abp-card-body>
</abp-card>
</abp-card>

12
modules/account/src/Volo.Abp.Account.Web/Pages/Account/LoggedOut.js

@ -1,5 +1,7 @@
document.addEventListener("DOMContentLoaded", function (event) {
setTimeout(function () {
window.location = document.getElementById("redirectButton").getAttribute("href");
}, 3000)
});
document.addEventListener('DOMContentLoaded', function (event) {
setTimeout(function () {
window.location = document
.getElementById('redirectButton')
.getAttribute('href');
}, 3000);
});

39
modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.js

@ -1,20 +1,23 @@
(function ($) {
var l = abp.localization.getResource('AbpAccount');
var _profileService = volo.abp.identity.profile;
$("#ChangePasswordForm").submit(function (e) {
$('#ChangePasswordForm').submit(function (e) {
e.preventDefault();
if (!$("#ChangePasswordForm").valid()) {
if (!$('#ChangePasswordForm').valid()) {
return false;
}
var input = $("#ChangePasswordForm").serializeFormToObject().changePasswordInfoModel;
var input = $('#ChangePasswordForm').serializeFormToObject()
.changePasswordInfoModel;
if (input.newPassword != input.newPasswordConfirm || input.currentPassword == '') {
abp.message.error(l("NewPasswordConfirmFailed"));
if (
input.newPassword != input.newPasswordConfirm ||
input.currentPassword == ''
) {
abp.message.error(l('NewPasswordConfirmFailed'));
return;
}
@ -22,29 +25,23 @@
return;
}
_profileService.changePassword(
input
).then(function (result) {
abp.message.success(l("PasswordChanged"));
_profileService.changePassword(input).then(function (result) {
abp.message.success(l('PasswordChanged'));
});
});
$("#PersonalSettingsForm").submit(function (e) {
$('#PersonalSettingsForm').submit(function (e) {
e.preventDefault();
if (!$("#PersonalSettingsForm").valid()) {
if (!$('#PersonalSettingsForm').valid()) {
return false;
}
var input = $("#PersonalSettingsForm").serializeFormToObject().personalSettingsInfoModel;
var input = $('#PersonalSettingsForm').serializeFormToObject()
.personalSettingsInfoModel;
_profileService.update(
input
).then(function (result) {
abp.notify.success(l("PersonalSettingsSaved"));
_profileService.update(input).then(function (result) {
abp.notify.success(l('PersonalSettingsSaved'));
});
});
})(jQuery);
})(jQuery);

5
modules/audit-logging/.prettierrc

@ -0,0 +1,5 @@
{
"singleQuote": true,
"useTabs": false,
"tabWidth": 4
}

4
modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditingStore.cs

@ -57,8 +57,8 @@ namespace Volo.Abp.AuditLogging
using (var uow = UnitOfWorkManager.Begin(true))
{
await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, auditInfo));
await uow.SaveChangesAsync();
await uow.CompleteAsync();
}
}
}
}
}

5
modules/background-jobs/.prettierrc

@ -0,0 +1,5 @@
{
"singleQuote": true,
"useTabs": false,
"tabWidth": 4
}

5
modules/blob-storing-database/.prettierrc

@ -0,0 +1,5 @@
{
"singleQuote": true,
"useTabs": false,
"tabWidth": 4
}

5
modules/blogging/.prettierrc

@ -0,0 +1,5 @@
{
"singleQuote": true,
"useTabs": false,
"tabWidth": 4
}

10
modules/blogging/Volo.Blogging.sln

@ -59,6 +59,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Blogging.Admin.HttpApi
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Blogging.Admin.Web", "src\Volo.Blogging.Admin.Web\Volo.Blogging.Admin.Web.csproj", "{DB75CA32-96A5-4D10-8DD0-E62A3D0DDBCB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{9FAD5B78-0577-4500-92D5-DC86E05F773C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Blogging.Application.Contracts.Shared", "src\Volo.Blogging.Application.Contracts.Shared\Volo.Blogging.Application.Contracts.Shared.csproj", "{E28EBBE0-8EB7-4FC1-9267-E6D30993EAE4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -153,6 +157,10 @@ Global
{DB75CA32-96A5-4D10-8DD0-E62A3D0DDBCB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DB75CA32-96A5-4D10-8DD0-E62A3D0DDBCB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DB75CA32-96A5-4D10-8DD0-E62A3D0DDBCB}.Release|Any CPU.Build.0 = Release|Any CPU
{E28EBBE0-8EB7-4FC1-9267-E6D30993EAE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E28EBBE0-8EB7-4FC1-9267-E6D30993EAE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E28EBBE0-8EB7-4FC1-9267-E6D30993EAE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E28EBBE0-8EB7-4FC1-9267-E6D30993EAE4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -183,6 +191,8 @@ Global
{59BBAF94-CC8E-4313-9143-F2F5C36A7C45} = {BE2A423C-271E-469A-AD90-5640DEBEE9C1}
{58A63CC9-C886-448B-AB4E-068600294D86} = {BE2A423C-271E-469A-AD90-5640DEBEE9C1}
{DB75CA32-96A5-4D10-8DD0-E62A3D0DDBCB} = {BE2A423C-271E-469A-AD90-5640DEBEE9C1}
{9FAD5B78-0577-4500-92D5-DC86E05F773C} = {42BF26EF-B8C7-42DC-9FFB-3653109B7776}
{E28EBBE0-8EB7-4FC1-9267-E6D30993EAE4} = {9FAD5B78-0577-4500-92D5-DC86E05F773C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F2BAE819-78D4-407A-9201-22473B2850B0}

12
modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo.Blogging.Admin.Application.Contracts.csproj

@ -2,7 +2,7 @@
<Import Project="..\..\..\..\configureawait.props" />
<Import Project="..\..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyName>Volo.Blogging.Admin.Application.Contracts</AssemblyName>
@ -12,15 +12,7 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Blogging.Domain.Shared\Volo.Blogging.Domain.Shared.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Ddd.Application\Volo.Abp.Ddd.Application.csproj" />
<ProjectReference Include="..\Volo.Blogging.Application.Contracts.Shared\Volo.Blogging.Application.Contracts.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="3.1.5" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Volo\Blogging\Admin\Localization\Resources\Blogging\Admin\ApplicationContracts\*.json" />
</ItemGroup>
</Project>

14
modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/BloggingAdminApplicationContractsModule.cs

@ -5,22 +5,12 @@ using Volo.Blogging.Localization;
namespace Volo.Blogging.Admin
{
[DependsOn(typeof(BloggingDomainSharedModule))]
[DependsOn(typeof(BloggingApplicationContractsSharedModule))]
public class BloggingAdminApplicationContractsModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<BloggingAdminApplicationContractsModule>();
});
Configure<AbpLocalizationOptions>(options =>
{
options.Resources
.Get<BloggingResource>()
.AddVirtualJson("Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts");
});
}
}
}

25
modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/BloggingAdminPermissionDefinitionProvider.cs

@ -1,25 +0,0 @@
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Localization;
using Volo.Blogging.Localization;
namespace Volo.Blogging.Admin
{
public class BloggingAdminPermissionDefinitionProvider : PermissionDefinitionProvider
{
public override void Define(IPermissionDefinitionContext context)
{
var bloggingGroup = context.AddGroup(BloggingAdminPermissions.GroupName, L("Permission:BloggingAdmin"));
var blogs = bloggingGroup.AddPermission(BloggingAdminPermissions.Blogs.Default, L("Permission:Blogs"));
blogs.AddChild(BloggingAdminPermissions.Blogs.Management, L("Permission:Management"));
blogs.AddChild(BloggingAdminPermissions.Blogs.Update, L("Permission:Edit"));
blogs.AddChild(BloggingAdminPermissions.Blogs.Delete, L("Permission:Delete"));
blogs.AddChild(BloggingAdminPermissions.Blogs.Create, L("Permission:Create"));
}
private static LocalizableString L(string name)
{
return LocalizableString.Create<BloggingResource>(name);
}
}
}

23
modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/BloggingAdminPermissions.cs

@ -1,23 +0,0 @@
using Volo.Abp.Reflection;
namespace Volo.Blogging.Admin
{
public class BloggingAdminPermissions
{
public const string GroupName = "Blogging.Admin";
public static class Blogs
{
public const string Default = GroupName + ".Blog";
public const string Management = Default + ".Management";
public const string Delete = Default + ".Delete";
public const string Update = Default + ".Update";
public const string Create = Default + ".Create";
}
public static string[] GetAll()
{
return ReflectionHelper.GetPublicConstantsRecursively(typeof(BloggingAdminPermissions));
}
}
}

11
modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/cs.json

@ -1,11 +0,0 @@
{
"culture": "cs",
"texts": {
"Permission:BloggingAdmin": "Blog",
"Permission:Blogs": "Blogy",
"Permission:Management": "Správa",
"Permission:Edit": "Upravit",
"Permission:Create": "Vytvořit",
"Permission:Delete": "Smazat"
}
}

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

@ -1,11 +0,0 @@
{
"culture": "de",
"texts": {
"Permission:BloggingAdmin": "Blog",
"Permission:Blogs": "Blogs",
"Permission:Management": "Verwaltung",
"Permission:Edit": "Bearbeiten",
"Permission:Create": "Erstellen",
"Permission:Delete": "Löschen"
}
}

11
modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/en.json

@ -1,11 +0,0 @@
{
"culture": "en",
"texts": {
"Permission:BloggingAdmin": "Blogging",
"Permission:Blogs": "Blogs",
"Permission:Management": "Management",
"Permission:Edit": "Edit",
"Permission:Create": "Create",
"Permission:Delete": "Delete"
}
}

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

@ -1,11 +0,0 @@
{
"culture": "nl",
"texts": {
"Permission:BloggingAdmin": "Blog",
"Permission:Blogs": "Blogs",
"Permission:Management": "Beheer",
"Permission:Edit": "Bewerk",
"Permission:Create": "Maak aan",
"Permission:Delete": "Verwijder"
}
}

11
modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/pl-PL.json

@ -1,11 +0,0 @@
{
"culture": "pl-PL",
"texts": {
"Permission:BloggingAdmin": "Blog",
"Permission:Blogs": "Blogi",
"Permission:Management": "Zarządzanie",
"Permission:Edit": "Edytuj",
"Permission:Create": "Utwórz",
"Permission:Delete": "Usuń"
}
}

11
modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/pt-BR.json

@ -1,11 +0,0 @@
{
"culture": "pt-BR",
"texts": {
"Permission:BloggingAdmin": "Blog",
"Permission:Blogs": "Blogs",
"Permission:Management": "Gerenciamento",
"Permission:Edit": "Editar",
"Permission:Create": "Criar",
"Permission:Delete": "Excluir"
}
}

11
modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/sl.json

@ -1,11 +0,0 @@
{
"culture": "sl",
"texts": {
"Permission:BloggingAdmin": "Blog",
"Permission:Blogs": "Blogi",
"Permission:Management": "Upravljanje",
"Permission:Edit": "Urejanje",
"Permission:Create": "Ustvarjanje",
"Permission:Delete": "Brisanje"
}
}

11
modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/tr.json

@ -1,11 +0,0 @@
{
"culture": "tr",
"texts": {
"Permission:BloggingAdmin": "Blog",
"Permission:Blogs": "Bloglar",
"Permission:Management": "Yönetme",
"Permission:Edit": "Düzenle",
"Permission:Create": "Ekle",
"Permission:Delete": "Sil"
}
}

11
modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/vi.json

@ -1,11 +0,0 @@
{
"culture": "vi",
"texts": {
"Permission:BloggingAdmin": "Blog",
"Permission:Blogs": "Blogs",
"Permission:Management": "Quản lý",
"Permission:Edit": "Sửa",
"Permission:Create": "Tạo",
"Permission:Delete": "Xóa"
}
}

11
modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/zh-Hans.json

@ -1,11 +0,0 @@
{
"culture": "zh-Hans",
"texts": {
"Permission:BloggingAdmin": "博客",
"Permission:Blogs": "博客",
"Permission:Management": "管理",
"Permission:Edit": "编辑",
"Permission:Create": "创建",
"Permission:Delete": "删除"
}
}

11
modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/zh-Hant.json

@ -1,11 +0,0 @@
{
"culture": "zh-Hant",
"texts": {
"Permission:BloggingAdmin": "部落格",
"Permission:Blogs": "部落格",
"Permission:Management": "管理",
"Permission:Edit": "標及",
"Permission:Create": "新增",
"Permission:Delete": "刪除"
}
}

6
modules/blogging/src/Volo.Blogging.Admin.Application/Volo/Blogging/Admin/Blogs/BlogManagementAppService.cs

@ -32,7 +32,7 @@ namespace Volo.Blogging.Admin.Blogs
return ObjectMapper.Map<Blog, BlogDto>(blog);
}
[Authorize(BloggingAdminPermissions.Blogs.Create)]
[Authorize(BloggingPermissions.Blogs.Create)]
public async Task<BlogDto> CreateAsync(CreateBlogDto input)
{
var newBlog = await _blogRepository.InsertAsync(new Blog(GuidGenerator.Create(), input.Name, input.ShortName)
@ -43,7 +43,7 @@ namespace Volo.Blogging.Admin.Blogs
return ObjectMapper.Map<Blog, BlogDto>(newBlog);
}
[Authorize(BloggingAdminPermissions.Blogs.Update)]
[Authorize(BloggingPermissions.Blogs.Update)]
public async Task<BlogDto> UpdateAsync(Guid id, UpdateBlogDto input)
{
var blog = await _blogRepository.GetAsync(id);
@ -55,7 +55,7 @@ namespace Volo.Blogging.Admin.Blogs
return ObjectMapper.Map<Blog, BlogDto>(blog);
}
[Authorize(BloggingAdminPermissions.Blogs.Delete)]
[Authorize(BloggingPermissions.Blogs.Delete)]
public async Task DeleteAsync(Guid id)
{
await _blogRepository.DeleteAsync(id);

4
modules/blogging/src/Volo.Blogging.Admin.Web/BloggingAdminMenuContributor.cs

@ -18,12 +18,12 @@ namespace Volo.Blogging.Admin
{
var l = context.GetLocalizer<BloggingResource>();
if (await context.IsGrantedAsync(BloggingAdminPermissions.Blogs.Management))
if (await context.IsGrantedAsync(BloggingPermissions.Blogs.Management))
{
var managementRootMenuItem = new ApplicationMenuItem("BlogManagement", l["Menu:BlogManagement"]);
//TODO: Using the same permission. Reconsider.
if (await context.IsGrantedAsync(BloggingAdminPermissions.Blogs.Management))
if (await context.IsGrantedAsync(BloggingPermissions.Blogs.Management))
{
managementRootMenuItem.AddItem(new ApplicationMenuItem("BlogManagement.Blogs", l["Menu:Blogs"], "~/Blogging/Admin/Blogs"));
}

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

@ -24,7 +24,7 @@ namespace Volo.Blogging.Admin.Pages.Blogging.Admin.Blogs
public virtual async Task<ActionResult> OnGetAsync()
{
if (!await _authorization.IsGrantedAsync(BloggingAdminPermissions.Blogs.Create))
if (!await _authorization.IsGrantedAsync(BloggingPermissions.Blogs.Create))
{
return Redirect("/");
}

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

Loading…
Cancel
Save