Browse Source

Merge branch 'dev' of https://github.com/abpframework/abp into german-dutch-encoding

pull/4637/head
Necati Meral 6 years ago
parent
commit
6a2cbde194
  1. 1
      build/common.ps1
  2. 4
      common.props
  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. 8
      docs/zh-Hans/Local-Event-Bus.md
  33. 4
      docs/zh-Hans/Repositories.md
  34. 12
      docs/zh-Hans/Tutorials/Part-2.md
  35. 36
      docs/zh-Hans/UI/Angular/Component-Replacement.md
  36. 1
      docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Form-elements.md
  37. 2
      docs/zh-Hans/docs-nav.json
  38. 27
      framework/src/Volo.Abp.AspNetCore.Authentication.OAuth/Properties/launchSettings.json
  39. 27
      framework/src/Volo.Abp.AspNetCore.MultiTenancy/Properties/launchSettings.json
  40. 27
      framework/src/Volo.Abp.AspNetCore.Mvc.Client/Properties/launchSettings.json
  41. 31
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/layout.css
  42. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/AbpAspNetCoreMvcUiThemeSharedDemoModule.cs
  43. 10
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.csproj
  44. 27
      framework/src/Volo.Abp.AspNetCore.Serilog/Properties/launchSettings.json
  45. 27
      framework/src/Volo.Abp.AspNetCore.SignalR/Properties/launchSettings.json
  46. 27
      framework/src/Volo.Abp.AspNetCore.TestBase/Properties/launchSettings.json
  47. 27
      framework/src/Volo.Abp.AspNetCore/Properties/launchSettings.json
  48. 4
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/AbpExceptionHandlingMiddleware.cs
  49. 15
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GenerateProxyCommand.cs
  50. 14
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs
  51. 3
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/SolutionRenameStep.cs
  52. 6
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AngularEnvironmentFilePortChangeForSeparatedIdentityServersStep.cs
  53. 72
      framework/src/Volo.Abp.Core/System/AbpStringExtensions.cs
  54. 27
      framework/src/Volo.Abp.Http.Client.IdentityModel.Web/Properties/launchSettings.json
  55. 2
      framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TZConvertTimezoneProvider.cs
  56. 2
      framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json
  57. 6
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/Pages/Components/Dropdowns/Index.cshtml
  58. 12
      framework/test/Volo.Abp.Core.Tests/System/StringExtensions_Tests.cs
  59. 5
      modules/account/.prettierrc
  60. 5
      modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/cs.json
  61. 12
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/LoggedOut.cshtml
  62. 12
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/LoggedOut.js
  63. 39
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.js
  64. 5
      modules/audit-logging/.prettierrc
  65. 4
      modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditingStore.cs
  66. 5
      modules/background-jobs/.prettierrc
  67. 5
      modules/blob-storing-database/.prettierrc
  68. 5
      modules/blogging/.prettierrc
  69. 10
      modules/blogging/Volo.Blogging.sln
  70. 12
      modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo.Blogging.Admin.Application.Contracts.csproj
  71. 14
      modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/BloggingAdminApplicationContractsModule.cs
  72. 25
      modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/BloggingAdminPermissionDefinitionProvider.cs
  73. 23
      modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/BloggingAdminPermissions.cs
  74. 11
      modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/cs.json
  75. 11
      modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/de.json
  76. 11
      modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/en.json
  77. 11
      modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/nl.json
  78. 11
      modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/pl-PL.json
  79. 11
      modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/pt-BR.json
  80. 11
      modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/sl.json
  81. 11
      modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/tr.json
  82. 11
      modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/vi.json
  83. 11
      modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/zh-Hans.json
  84. 11
      modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/zh-Hant.json
  85. 6
      modules/blogging/src/Volo.Blogging.Admin.Application/Volo/Blogging/Admin/Blogs/BlogManagementAppService.cs
  86. 4
      modules/blogging/src/Volo.Blogging.Admin.Web/BloggingAdminMenuContributor.cs
  87. 2
      modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/Create.cshtml.cs
  88. 2
      modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/Edit.cshtml.cs
  89. 2
      modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/Index.cshtml
  90. 2
      modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/Index.cshtml.cs
  91. 8
      modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/create.js
  92. 8
      modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/edit.js
  93. 116
      modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/index.js
  94. 3
      modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/FodyWeavers.xml
  95. 30
      modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/FodyWeavers.xsd
  96. 18
      modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo.Blogging.Application.Contracts.Shared.csproj
  97. 15
      modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo/Blogging/BloggingApplicationContractsSharedModule.cs
  98. 40
      modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo/Blogging/BloggingPermissionDefinitionProvider.cs
  99. 47
      modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo/Blogging/BloggingPermissions.cs
  100. 12
      modules/blogging/src/Volo.Blogging.Application.Contracts/Volo.Blogging.Application.Contracts.csproj

1
build/common.ps1

@ -22,6 +22,5 @@ $solutionPaths = (
"../modules/virtual-file-explorer",
"../templates/module/aspnet-core",
"../templates/app/aspnet-core",
"../samples/MicroserviceDemo",
"../abp_io/AbpIoLocalization"
)

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>

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' ...]

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"
}
}
}

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);
}
}
}
}

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",

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
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}"
}
}

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

@ -3,16 +3,20 @@
@using Volo.Abp.Account.Localization
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Abp.Account.Web.Pages.Account
@using Volo.Abp.AspNetCore.Mvc.UI.Theming
@inject IThemeManager ThemeManager
@inject IHtmlLocalizer<AccountResource> L
@{
Layout = ThemeManager.CurrentTheme.GetApplicationLayout();
}
@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>
@ -28,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("/");
}

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

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

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

@ -28,7 +28,7 @@
<h2>@L["Blogs"]</h2>
</abp-column>
<abp-column size-md="_6" class="text-right">
@if (await Authorization.IsGrantedAsync(BloggingAdminPermissions.Blogs.Create))
@if (await Authorization.IsGrantedAsync(BloggingPermissions.Blogs.Create))
{
<abp-button icon="plus" text="@L["CreateANewBlog"].Value" button-type="Primary" id="CreateNewBlogButtonId"></abp-button>
}

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

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

8
modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/create.js

@ -3,12 +3,10 @@ $(function () {
abp.modals.blogCreate = function () {
var initModal = function (publicApi, args) {
var $form = publicApi.getForm();
};
return {
initModal: initModal
}
initModal: initModal,
};
};
});
});

8
modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/edit.js

@ -3,12 +3,10 @@ $(function () {
abp.modals.blogEdit = function () {
var initModal = function (publicApi, args) {
var $form = publicApi.getForm();
};
return {
initModal: initModal
}
initModal: initModal,
};
};
});
});

116
modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/index.js

@ -1,73 +1,84 @@
$(function () {
var l = abp.localization.getResource('Blogging');
var _createModal = new abp.ModalManager(abp.appPath + 'Blogging/Admin/Blogs/Create');
var _editModal = new abp.ModalManager(abp.appPath + 'Blogging/Admin/Blogs/Edit');
var _createModal = new abp.ModalManager(
abp.appPath + 'Blogging/Admin/Blogs/Create'
);
var _editModal = new abp.ModalManager(
abp.appPath + 'Blogging/Admin/Blogs/Edit'
);
var _dataTable = $('#BlogsTable').DataTable(abp.libs.datatables.normalizeConfiguration({
processing: true,
serverSide: true,
paging: false,
info: false,
scrollX: true,
searching: false,
autoWidth: false,
scrollCollapse: true,
order: [[3, "desc"]],
ajax: abp.libs.datatables.createAjax(volo.blogging.admin.blogManagement.getList),
columnDefs: [
{
rowAction: {
items:
[
var _dataTable = $('#BlogsTable').DataTable(
abp.libs.datatables.normalizeConfiguration({
processing: true,
serverSide: true,
paging: false,
info: false,
scrollX: true,
searching: false,
autoWidth: false,
scrollCollapse: true,
order: [[3, 'desc']],
ajax: abp.libs.datatables.createAjax(
volo.blogging.admin.blogManagement.getList
),
columnDefs: [
{
rowAction: {
items: [
{
text: l('Edit'),
visible: abp.auth.isGranted('Blogging.Admin.Blog.Update'),
visible: abp.auth.isGranted(
'Blogging.Blog.Update'
),
action: function (data) {
_editModal.open({
blogId: data.record.id
blogId: data.record.id,
});
}
},
},
{
text: l('Delete'),
visible: abp.auth.isGranted('Blogging.Admin.Blog.Delete'),
confirmMessage: function (data) { return l('BlogDeletionWarningMessage') },
visible: abp.auth.isGranted(
'Blogging.Blog.Delete'
),
confirmMessage: function (data) {
return l('BlogDeletionWarningMessage');
},
action: function (data) {
volo.blogging.admin.blogManagement
.delete(data.record.id)
.then(function () {
_dataTable.ajax.reload();
});
}
}
]
}
},
{
target: 1,
data: "name"
},
{
target: 2,
data: "shortName"
},
{
target: 3,
data: "creationTime",
render: function (date) {
return date;
}
},
{
target: 4,
data: "description"
}
]
}));
},
},
],
},
},
{
target: 1,
data: 'name',
},
{
target: 2,
data: 'shortName',
},
{
target: 3,
data: 'creationTime',
render: function (date) {
return date;
},
},
{
target: 4,
data: 'description',
},
],
})
);
$("#CreateNewBlogButtonId").click(function () {
$('#CreateNewBlogButtonId').click(function () {
_createModal.open();
});
@ -78,5 +89,4 @@
_editModal.onResult(function () {
_dataTable.ajax.reload();
});
});

3
modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/FodyWeavers.xml

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

30
modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/FodyWeavers.xsd

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

18
modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo.Blogging.Application.Contracts.Shared.csproj

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\configureawait.props" />
<Import Project="..\..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyName>Volo.Blogging.Application.Contracts.Shared</AssemblyName>
<PackageId>Volo.Blogging.Application.Contracts.Shared</PackageId>
<RootNamespace />
</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" />
</ItemGroup>
</Project>

15
modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo/Blogging/BloggingApplicationContractsSharedModule.cs

@ -0,0 +1,15 @@
using Volo.Abp.Application;
using Volo.Abp.Modularity;
namespace Volo.Blogging
{
[DependsOn(typeof(BloggingDomainSharedModule),
typeof(AbpDddApplicationModule))]
public class BloggingApplicationContractsSharedModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
}
}
}

40
modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo/Blogging/BloggingPermissionDefinitionProvider.cs

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

47
modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo/Blogging/BloggingPermissions.cs

@ -0,0 +1,47 @@
using Volo.Abp.Reflection;
namespace Volo.Blogging
{
public class BloggingPermissions
{
public const string GroupName = "Blogging";
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 class Posts
{
public const string Default = GroupName + ".Post";
public const string Delete = Default + ".Delete";
public const string Update = Default + ".Update";
public const string Create = Default + ".Create";
}
public static class Tags
{
public const string Default = GroupName + ".Tag";
public const string Delete = Default + ".Delete";
public const string Update = Default + ".Update";
public const string Create = Default + ".Create";
}
public static class Comments
{
public const string Default = GroupName + ".Comment";
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(BloggingPermissions));
}
}
}

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

@ -12,17 +12,7 @@
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Volo\Blogging\Localization\Resources\Blogging\ApplicationContracts\*.json" />
<None Remove="Volo\Blogging\Localization\Resources\Blogging\ApplicationContracts\*.json" />
<ProjectReference Include="..\Volo.Blogging.Application.Contracts.Shared\Volo.Blogging.Application.Contracts.Shared.csproj" />
</ItemGroup>
<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" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="3.1.5" />
</ItemGroup>
</Project>

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

Loading…
Cancel
Save